【若川视野 x 源码共读】第31期 | p-limit

268 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

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

p-limit,是用来对前端并发请求进行控制的

并发是指一个应用程序中存在多个任务在执行,同时刻或者说看起来同一时刻(并发)这些任务都在执行

参考

www.yuque.com/ruochuan12/…

github.com/sindresorhu…

github1s.com/sindresorhu…

www.yuque.com/ruochuan12/…

并发控制的必要

对于浏览器来说,对于同一域名下的并发请求数量有限制,比如Chrome中只允许6个并发请求,多出来的请求只能排队,等待发送。不过在http2中同一个域名下无限制请求。

对于服务端,多个并发请求可能会对服务端产生压力。

网络请过程需要经过DNS寻址、与服务器建立连接、发送数据、等待服务器响应、接收数据这样一个漫长而复杂的过程,如等待时间过长则可能造成不好的用户体验。所以我认为控制并发数,让服务端减轻压力,让一些请求先行完成,一定程度上对用户友好。

使用

import pLimit from 'p-limit';
//设置并发限制
const limit = pLimit(1);
//用limit包裹 异步请求
const input = [
    limit(() => fetchSomething('foo')),
    limit(() => fetchSomething('bar')),
    limit(() => doSomething())
];
​
// Only one promise is run at once
const result = await Promise.all(input);
console.log(result);

源码

因为用到了yocto-queue,先看这个库的用法,不知道为什么最近看到好多的彩虹小马

www.npmjs.com/package/yoc…

import Queue from 'yocto-queue';
​
const queue = new Queue();
//先进先出。
queue.enqueue('🦄');
queue.enqueue('🌈');
​
console.log(queue.size);
//=> 2
​
console.log(...queue);
//=> '🦄 🌈'
​
console.log(queue.dequeue());
//=> '🦄'
​
console.log(queue.dequeue());
//=> '🌈'
  • index.js

按流程看,先执行pLimit函数,传入运行并行的最大数,看源码 返回 generator

再用limit包裹函数,传入。那么执行 generator函数。具体看源码

import Queue from 'yocto-queue';
​
export default function pLimit(concurrency) {
。。。
​
    const queue = new Queue();
    let activeCount = 0;
​
    const next = () => {
        // 执行完 任务 后,当前执行任务-1
        activeCount--;
    // 有任务结束去掉了,并发限制肯定没满,看看能不能加
        if (queue.size > 0) {
            queue.dequeue()();
        }
    };
​
    const run = async (fn, resolve, args) => {
        //每执行一次,当前执行任务加一
        activeCount++;
        // 执行fn。
        const result = (async () => fn(...args))();
    // 得到结果要返回
        resolve(result);
​
        try {
            await result;
        } catch {}
    // 执行完 后 走next
        next();
    };
​
    const enqueue = (fn, resolve, args) => {
        // 以上面的例子举例,这里先推三个 run 任务进队里,同步加入
        queue.enqueue(run.bind(undefined, fn, resolve, args));
    //这里是异步任务,所以会在上面所有limit任务进队才执行。
        (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`.
            // 老实说我不太懂这个Promise.resolve()干嘛用的
            await Promise.resolve();
    // activeCount:当前执行任务数,concurrency:并发限制,队列有任务
            if (activeCount < concurrency && queue.size > 0) {
                // 我一开始以为是队列连续推出两个,直到我调试才知道他是队列推出一个去执行,接着看run
                queue.dequeue()();
            }
        })();
    };
    // 返回promise,当promise.all启动时就执行enqueue
    const generator = (fn, ...args) => new Promise(resolve => {
        //这里的fn就是传入的异步请求函数。转到enqueue
        enqueue(fn, resolve, args);
    });
​
。。。
​
    return generator;
}
​

其他

  • 有点有趣,执行顺序从下看到上。真讨厌这样。

总结流程

1 传入并发限制最大数,返回个方法供 加异步函数

2 使用数组 组装 要限制并发的任务们 ,通过上面的方法传入 异步函数 返回 promise

3 使用promise.all 包裹上面的 队列,以便让 promise 去执行

4 执行的时候,利用同步把任务都存队列里,再通过当前执行任务数和并发限制数从队列里拿任务出来执行