本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第31期 | p-limit 限制并发数 点击了解本期详情一起参与。
本文涉及
闭包
队列
首先,我们先看readme
- 这个库是用来控制异步并发数量的
那么,我们为什么要控制异步并发数量呢,他的具体使用场景有哪些
使用场景
- 页面加载很多个请求,可能会引起卡顿
- 多个请求并发时,可能会受到浏览器的请求数量限制,处于后面的请求可能有超时的风险
源码分析
我们尝试调试源码
yocto-queue可以参考之前的文章介绍:juejin.cn/post/714128…
import Queue from "yocto-queue";
export default function pLimit(concurrency) {
// 判断限制数量是整数且 concurrency > 0
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 = () => {
// 计数器 -1
activeCount--;
// 如果队列中还有元素,则继续执行
if (queue.size > 0) {
queue.dequeue()();
}
};
const run = async (fn, resolve, args) => {
// 计数器+1
activeCount++;
const result = (async () => fn(...args))();
resolve(result);
// 保证了next的顺序
try {
await result;
} catch {}
next();
};
const enqueue = (fn, resolve, args) => {
// 将异步方法入列,闭包的关系,所有的方法公用一个队列控制
queue.enqueue(run.bind(undefined, fn, resolve, args));
(async () => {
// This function needs to wait until the next microtask before comparing
// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
// when the run function is dequeued and called. The comparison in the if-statement
// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
// 得到最新的 activeCount
await Promise.resolve();
// 如果当前执行的方法数小于限制数,则队列推出一个元素执行
if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()();
}
})();
};
// 返回一个Promise,将异步方法入队
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;
}
整个流程大概就是
- 使用了一个队列来管理异步函数执行
- 通过
enqueue,run,next来控制并发的数量,从而达到限流的目的
总结
这个库使用了队列来和activeCount来控制当前执行的并发数,实现了异步函数的顺序执行,并对此做出并发限制
库中使用了链表模拟队列,在shift操作中时间复杂度更低,进一步地优化性能
这个库的代码写法简洁,并且考虑到了边界异常等情况,这些都是值得我们去学习的