【源码共读】| p-limit 限制并发数

1,371 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

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

【若川视野 x 源码共读】第31期 | p-limit 限制并发数 点击了解本期详情一起参与

本文涉及

闭包

队列

源码地址:github.com/sindresorhu…

首先,我们先看readme

image-20220925001515352

  • 这个库是用来控制异步并发数量的

那么,我们为什么要控制异步并发数量呢,他的具体使用场景有哪些

使用场景

  • 页面加载很多个请求,可能会引起卡顿
  • 多个请求并发时,可能会受到浏览器的请求数量限制,处于后面的请求可能有超时的风险

源码分析

我们尝试调试源码

image-20220925134321430

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;
}

整个流程大概就是

  • 使用了一个队列来管理异步函数执行
  • 通过enqueuerunnext来控制并发的数量,从而达到限流的目的

image-20220925143833254


总结

这个库使用了队列来和activeCount来控制当前执行的并发数,实现了异步函数的顺序执行,并对此做出并发限制

库中使用了链表模拟队列,在shift操作中时间复杂度更低,进一步地优化性能

这个库的代码写法简洁,并且考虑到了边界异常等情况,这些都是值得我们去学习的


参考文章

juejin.cn/post/704527…

mp.weixin.qq.com/s/6LsPMIHdI…