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

201 阅读3分钟

学习笔记:zhuanlan.zhihu.com/p/549829103
juejin.cn/post/715877…

使用案例

import delay from 'delay';
import timeSpan from 'time-span';
const end = timeSpan()
const fetchData = async () => {
    await delay(1000) // 延时1s
    return 10
}
const fetchData1 = async() => {
    await delay(2000) // 延时2s
    return 20
}
const fetchData2 = async () => {
    await delay(3000) // 延时3s
    return 30
}
const input = [
    fetchData(),
    fetchData1(),
    fetchData2()
];
const result = await Promise.all(input)
console.log(result); // [ 10, 20, 30 ]
console.log(end()) // 3011.592865 运行的毫秒数

使用p-limit来限制一下并发数

import pLimit from 'p-limit';
import delay from 'delay';
import timeSpan from 'time-span';
const limit = pLimit(1);
const end = timeSpan()

const input = [
	limit(() => fetchData()),
	limit(() => fetchData1()),
	limit(() => fetchData2())
];

const result = await Promise.all(input)
console.log(result); // [ 10, 20, 30 ]
console.log(end()) // 6012.97549 运行的毫秒数

限制并发

并发

代码的执行一般是按顺序执行的,所以同步任务是不存在并发的。但如果是异步任务的话,即使有先后顺序,它的执行的结果也是不受控制的。比如Promise.race()最终返回resolve的Promise是不确定的。

控制并发请求数-控制在并发数任务在pending的过程中,不再继续发送请求

代码

import Queue from 'yocto-queue';

export default function pLimit(concurrency) {
	//concurrency是不是整数且大于1
	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 () => {
			// 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`.
			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;
}

generator

export default function pLimit(concurrency) {
  //接收异步函数和剩余参数,调用Promise将resolve传递给enqueue函数。
  	const generator = (fn, ...args) => new Promise(resolve => {
		//每个 generator 函数执行会将一个异步函数压入队列。
  		enqueue(fn, resolve, args);
  	});
   return generator;
 }

enqueue 主要是用于处理异步函数的入队操作

//在p-limit中主要用到了yocto-queue的两个方法:enqueue(入队),dequeue(出队)。 
//多个 generator 函数会共用一个队列。
import Queue from 'yocto-queue';

 // 初始化队列
const queue = new Queue();
 // 初始化计数器
let activeCount = 0; 

const enqueue = (fn, resolve, args) => {
	 // 入队,使用run函数对异步函数fn进行包装 
		queue.enqueue(run.bind(undefined, fn, resolve, args));
		// 自执行的async函数,
		(async () => {
		//await Promise.resolve()也只是返回一个成功的状态。以便于获取最新的activeCount的值
			await Promise.resolve();
  		//如果当前的计数器小于规定的并发数,并且队列中还有未执行的函数,
			if (activeCount < concurrency && queue.size > 0) {
        //出队并执行run函数
				queue.dequeue()();
			}
		})();
	};

为什么使用async?

将异步函数出队执行的操作放入微任务队列,便于获得activeCount的最新值,与concurrency比较

await Promise.resolve() 作用

返回一个成功的状态。以便于获取最新的activeCount的值。

run 控制计数器的个数,执行异步函数

	const run = async (fn, resolve, args) => {
		activeCount++;  //执行异步函数,计数器加1

    //执行异步函数,并保存执行后的结果
		const result = (async () => fn(...args))();

  	//执行resolve函数
		resolve(result);

		try {
    	//执行后的通过
			await result;
		} catch {}
  	//下一步执行函数,必须在await result后面
		next();
	};

**next **异步函数执行完毕后的后续操作,包括对计数器减一,取出后续的异步任务。

const next = () => {
  	//计数器减一
		activeCount--;

  	//如果队列中还有任务,那么继续取出执行
		if (queue.size > 0) {
			queue.dequeue()();
		}
	};

辅助函数 activeCount(获取当前执行任务的个数)、pendingCount(当前等待任务个数,队列中的个数)、clearQueue(清空队列)

 Object.defineProperties(generator, {
 activeCount: {
   get: () => activeCount,
 },
 pendingCount: {
   get: () => queue.size,
 },
 clearQueue: {
   value: () => {
     queue.clear();
   },
 },
 });