p-limit 源码阅读学习笔记

1,058 阅读2分钟

我正在参与掘金会员专属活动-源码共读第一期,点击参与

前言

看到标题,我们先来想想什么是并发?其实从某种程度上来说,并发有点像同步,可以理解为同时进行,同时发生。比如说有两个任务,通过不断切换这两个任务来完成两个任务,这种情况就叫做并发。那么,为啥要限制并发呢?因为计算机的并处理能力都是有限的,为了使得处理有序,因此有必要限制同时处理请求的数量。所以今天我们来学习一下p-limit限制并发数的知识。

p-limit

p-limit使用pLimit来定义并发数,返回一个函数用于接收每一个异步任务,保证无论传入多少个异步任务,都始终根据传入的并发数量来进行执行。了解了它的相关概念,我们再来看看关于它的使用方法吧。下面这是官方的使用示例。

import pLimit from 'p-limit';
const limit = pLimit(1);
const input = [
	limit(() => fetchSomething('foo')),
	limit(() => fetchSomething('bar')),
	limit(() => doSomething())
];
const result = await Promise.all(input);
console.log(result);

从上面不难看出,关于它的使用方式还是比较简单的,清晰易懂。

源码解读

我们现在来看看p-limit的源码部分。源码部分并不多,前前后后大约六十多行。

import Queue from 'yocto-queue';
export default function pLimit(concurrency) {
   if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
      throw new TypeError('Expected `concurrency` to be a number from 1 and up');
   }
   const queue = new Queue();
   let activeCount = 0;
   const next = () => {
      activeCount--;
      if (queue.size > 0) {
         queue.dequeue()();
      }
   };
   const run = async (fn, resolve, args) => {
      activeCount++;
      const result = (async () => fn(...args))();
      resolve(result);
      try {
         await result;
      } catch {}
      next();
   };
   const enqueue = (fn, resolve, args) => {
      queue.enqueue(run.bind(undefined, fn, resolve, args));
      (async () => {
         await Promise.resolve();
         if (activeCount < concurrency && queue.size > 0) {
            queue.dequeue()();
         }
      })();
   };
   const generator = (fn, ...args) => new Promise(resolve => {
      enqueue(fn, resolve, args);
   });
   Object.defineProperties(generator, {
      activeCount: {
         get: () => activeCount,
      },
      pendingCount: {
         get: () => queue.size,
      },
      clearQueue: {
         value: () => {
            queue.clear();
         },
      },
   });
   return generator;
}

源码中引入了一个我们都比较熟悉的东西 —— yocto-queue,因此我们要有队列和链表的基础,关于yocto-queue的学习可以看看这篇文章 数组还是队列?yocto-queue 源码告诉你

虽然比较少,但是整体看下来代码会有点繁杂。首先它是定义了一个队列queue,然后里面又定义来一个异步函数run。在run函数之前有一个next函数,它的作用是减少当前并发数,并判断是否移出队列中的任务。任务如何移出呢?通过queue.dequeue()()来实现。

任务被移出后,开始执行异步函数run,内部进行一系列操作后在下一个任务中又执行next函数。

总结

今天学习的p-limit源码,虽然代码不多,但是里面的逻辑却不少,需要多看几遍来消化消化。我们学习的可能不止是一个p-limit,也学习到了同步异步以及这些async函数的执行顺序。所以需要多理解一下,里面知识点还是挺多的。