🐋async-limiter源码分析

164 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动~

本文对async-limit的源码进行解析,一步步实现并发控制\

🥳🥳🥳P.S. 欢迎点赞,评论,收藏~
🥳🥳🥳如有不足,欢迎指正哦~

async-limiter在干什么呢?

它主要实现任务的并发控制,来限制同时运行的任务数量。

这个场景在前端非常常见,比如大文件上传时,可以控制同时上传的分片数量,或者是进行大量http请求来或者资源时,或者处理长列表数据时,都能用得到。

如何实现async-limiter

  • 首先,得有一个用户维护的任务数组,用户把想要进行并发控制的任务塞进数组里
    P.S. 这个库中只实现了添加任务,并未实现移除,我想任务的移除还需要多个反馈机制,得考虑如果执行了怎么处理,没执行时可以移除,这块就留给后续更新吧~

    image.png

  • 其次,要限制同时运行中的任务数量,就需要有一个标识,来存放当前要运行的任务数,当这个标识小于concurrency时,任务将被执行,当大于concurrency时,任务将被挂起,直到标识小于concurrency,才继续执行。

    image.png

  • 接着,完善这个流程,定义一个run方法,来执行pending++,推进流程。同时还需要有一个done方法,标志着一个任务执行完毕,随后,要pending--,再调用run,来执行下一个任务。

    image.png

  • 到这里实现了流程上的推进,也实现了同时运行的任务数量限制/并发控制。那当开始运行时,用户往jobs任务数组里推任务,每推一个任务就调用一下run,只要pending的任务数小于concurrency,就一直执行任务,实现了同步执行。
    P.S.这里说的同步执行,是指js中的同步任务的执行方式,并不是同一时间点,真正的并发了,毕竟js是一个单线程。

    image.png

  • 最后,当所有jobs执行完毕,再去执行final的callback函数,那就要知道什么时候任务都执行完毕,每一个任务执行完毕都会调done方法,也会随后去调run方法,不管在哪个方法里判断,当pending为0时,就可以了说明,没有任务了,执行完毕了

    image.png

全部源码

读源码可以学到很多小知识,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;