【若川视野 x 源码共读】第31期 | p-limit 限制并发数

292 阅读2分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。

参考文章

Node.js 并发能力总结

p-limit作用

p-limit 用于限制node的并发数。

为什么要限制并发?

node.js支持高并发,但是对于被node调用的供应商来说,高并发是负担,是资源占用,需要限制在合理范围内。

p-limit实现关键

类似于我们在疫情期间排队等待做核酸一样,concurrency是采样医生的个数,activeCount是正在被采样的人的数量,quenue是等待采样的排队队伍,run函数类似于我们张嘴“啊”在采样, next函数是我们采样完成之后离开。

  1. activeCount统计正在执行的函数数量
  2. queue队列存入等待执行的函数
  3. run函数执行从queue队列出来的函数

源码:

import Queue from 'yocto-queue';

/**
 * 限制并发
 * @param {*} concurrency 正整数
 * @returns 
 */
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) {
			// 队列的第一个出队并执行run函数
			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) => {
		// bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
		// 传入null/undefined的时候将执行js全局对象浏览器中是window,其他环境是global
		// 排队等候
		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”与“concurrency”进行比较,
			// 因为当 run函数取消排队并调用时,“activeCount”会异步更新。
			// if 语句中的比较也需要异步进行,以获取“activeCount”的最新值。
			await Promise.resolve();

			// 如果计数器的数量小于传入的并发数,并且还有需要执行的请求,那么则执行队伍的第一个请求
			if (activeCount < concurrency && queue.size > 0) {
				// 队列的第一个出队并执行run函数
				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;
}