本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
使用
p-limit@4.0.0 使用时,需要用module方式引入,当前引入的js后缀名,需要改为mjs,才能在node中使用es module 模块化方案引入包。
// p-limit的pageage.json 设置了type:"module"
// 所以需要把js变成mjs 然后用es module 形式引入
import pLimit from "p-limit";
// 模拟请求
const testFetch = (params) => {
return new Promise((resolve) => {
setTimeout(resolve, 1000, params);
})
}
const limit = pLimit(3); // 限制并发数为3个
const input = [
limit(() => testFetch(1)),
limit(() => testFetch(2)),
limit(() => testFetch(3)),
limit(() => testFetch(4)),
limit(() => testFetch(5)),
limit(() => testFetch(6)),
];
(async () => {
const result = await Promise.all(input);
console.log(result); // 每次并发3个,所以两秒后返回 [1,2,3,4,5,6]
})()
源码
p-limit 用于限制node的并发数。
// https://github1s.com/sindresorhus/yocto-queue/blob/HEAD/index.js
import Queue from 'yocto-queue'; // 这个库的队列用双向链表写的,push shift 都是O(1),如果用数组 shift 是O(n)
export default function pLimit(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; // 活动数量0
const next = () => {
activeCount--; // 活动数量 - 1
// 下一个队列里面有值,就取出调用
if (queue.size > 0) {
queue.dequeue()(); // 出队 并执行
}
};
// 包装
const run = async (fn, resolve, args) => {
activeCount++; // 入队,活动数量+1
const result = (async () => fn(...args))(); // 执行
resolve(result); // 结果返回
try {
await result; // 为保证 next 的顺序,采用了 await result
} catch {}
next(); // 调用next
};
const enqueue = (fn, resolve, args) => {
queue.enqueue(run.bind(undefined, fn, resolve, args)); // 将传入的函数放到队列中,放之前需要用 run 函数包一下
(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 这个值,需要在一次微任务之后,才会更新为最新的。然后用最新的和 concurrency 比较
await Promise.resolve();
// 如果当前活动数 小于 并发数 并且 队列中还有值
if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()(); // 取出队列中的值,并调用
}
})();
};
// 返回一个 generator 函数, 参数会接收一个函数,函数可以传参数
const generator = (fn, ...args) => new Promise(resolve => {
enqueue(fn, resolve, args); // push到队列中
});
Object.defineProperties(generator, {
activeCount: {
get: () => activeCount,
},
pendingCount: {
get: () => queue.size,
},
clearQueue: {
value: () => {
queue.clear();
},
},
});
return generator;
}
总结
Number.isInteger()
- 判断当前数字是否是整数
- MDN Number.isInteger()
Number.POSITIVE_INFINITY
Number.POSITIVE_INFINITY属性和Infinity相同- MDN Number.POSITIVE_INFINITY
Infinity === Number.POSITIVE_INFINITY
// true
yocto-queue
- 双向链表写的队列
- 源码
自执行函数
- 在入队的时候,放入了一个自执行异步函数。来控制代码发生的时间。
const enqueue = (fn, resolve, args) => {
queue.enqueue(run.bind(undefined, fn, resolve, args)); // 将传入的函数放到队列中,放之前需要用 run 函数包一下
(async () => {
// 以保证执行顺序
await Promise.resolve();
if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()();
}
})();
};