说明
有时会遇到一个耗时异步过程(如转码、慢查询)可能被快速反复调用的情况。如果希望每次调用都能正确返回,而又不希望过程被重复执行消耗资源,可以使用下面的装饰器函数来包装过程。
注意,装饰器可以配置最大同步过程及等待时间,以有限增加和确保回收监听器,使用时应根据情况处理。
装饰器函数:
/**
* 异步过程(函数)状态同步
* @param asyncFunc 过程函数
* @param opts
* opts.maxProcess 最大同步过程数
* opts.timeout 最大同步等待时间
*/
function stateSynchronize(asyncFunc, opts) {
opts = opts || {};
opts.maxProcess = opts.maxProcess || 100;
opts.timeout = opts.timeout || 60 * 1000;
const keySet = new Set();
const emitter = new (require('events').EventEmitter)();
emitter.setMaxListeners(opts.maxProcess);
return async function (key, ...args) {
if (keySet.has(key)) {
return await (() => {
return new Promise((resolve, reject) => {
if (emitter.listeners('finish').length >= opts.maxProcess) {
return reject(new Error('too much process at the same time'));
}
setTimeout(() => {
return reject(new Error('process wait synchronization timeout'));
}, opts.timeout);
emitter.once('finish', ({key, ret}) => {
return resolve(ret);
});
});
})();
}
keySet.add(key);
let ret = await asyncFunc(...args);
keySet.delete(key);
emitter.emit('finish', {key, ret});
return ret;
}
}
测试
// 异步过程
async function work(key) {
await (function () {return new Promise(resolve => setTimeout(() => resolve(), 2000))})();
return key;
}
// 装饰
work = stateSynchronize(work);
// 执行
~async function() {
process.nextTick(async () => {
let key = 1;
let ret = await work(key, [key]);
console.log('1过程执行完毕,返回', ret);
});
setTimeout(async () => {
for (let i=0; i< 11; i++) {
process.nextTick(async () => {
let key = 1;
let ret = await work(key, [key]);
console.log('2过程执行完毕,返回', ret);
});
}
}, 1000);
setTimeout(async () => {
process.nextTick(async () => {
let key = 2;
let ret = await work(key, [key]);
console.log('3过程执行完毕,返回', ret);
});
}, 1000);
}();
// 结果中,2过程和1过程有同样的key,虽然2过程执行晚了1s,但仍会和1一起结束,同样返回。
网友评论