小知识,大挑战!本文正在参与“程序员必备小知识”创作活动~
本文对async-limit的源码进行解析,一步步实现并发控制\
🥳🥳🥳P.S. 欢迎点赞,评论,收藏~
🥳🥳🥳如有不足,欢迎指正哦~
async-limiter在干什么呢?
它主要实现任务的并发控制,来限制同时运行的任务数量。
这个场景在前端非常常见,比如大文件上传时,可以控制同时上传的分片数量,或者是进行大量http请求来或者资源时,或者处理长列表数据时,都能用得到。
如何实现async-limiter
-
首先,得有一个用户维护的任务数组,用户把想要进行并发控制的任务塞进数组里
P.S. 这个库中只实现了添加任务,并未实现移除,我想任务的移除还需要多个反馈机制,得考虑如果执行了怎么处理,没执行时可以移除,这块就留给后续更新吧~ -
其次,要限制同时运行中的任务数量,就需要有一个标识,来存放当前要运行的任务数,当这个标识小于concurrency时,任务将被执行,当大于concurrency时,任务将被挂起,直到标识小于concurrency,才继续执行。
-
接着,完善这个流程,定义一个run方法,来执行pending++,推进流程。同时还需要有一个done方法,标志着一个任务执行完毕,随后,要pending--,再调用run,来执行下一个任务。
-
到这里实现了流程上的推进,也实现了同时运行的任务数量限制/并发控制。那当开始运行时,用户往jobs任务数组里推任务,每推一个任务就调用一下run,只要pending的任务数小于concurrency,就一直执行任务,实现了同步执行。
P.S.这里说的同步执行,是指js中的同步任务的执行方式,并不是同一时间点,真正的并发了,毕竟js是一个单线程。 -
最后,当所有jobs执行完毕,再去执行final的callback函数,那就要知道什么时候任务都执行完毕,每一个任务执行完毕都会调done方法,也会随后去调run方法,不管在哪个方法里判断,当pending为0时,就可以了说明,没有任务了,执行完毕了
全部源码
读源码可以学到很多小知识,for instance:
- 判断instanceof
- bind
- Object.defineProperty
- 劫持Array.prototype上的方法
function Queue(options) {
if (!(this instanceof Queue)) {
return new Queue(options);
}
options = options || {};
this.concurrency = options.concurrency || Infinity;
this.pending = 0;
this.jobs = [];
this.cbs = [];
this._done = done.bind(this);
}
var arrayAddMethods = [
'push',
'unshift',
'splice'
];
arrayAddMethods.forEach(function(method) {
Queue.prototype[method] = function() {
var methodResult = Array.prototype[method].apply(this.jobs, arguments);
this._run();
return methodResult;
};
});
// Jobs pending + jobs to process (readonly).
Object.defineProperty(Queue.prototype, 'length', {
get: function() {
return this.pending + this.jobs.length;
}
});
Queue.prototype._run = function() {
if (this.pending === this.concurrency) {
return;
}
if (this.jobs.length) {
var job = this.jobs.shift();
this.pending++;
job(this._done);
this._run();
}
if (this.pending === 0) {
while (this.cbs.length !== 0) {
var cb = this.cbs.pop();
process.nextTick(cb);
}
}
};
Queue.prototype.onDone = function(cb) {
if (typeof cb === 'function') {
this.cbs.push(cb);
this._run();
}
};
function done() {
this.pending--;
this._run();
}
module.exports = Queue;