前端并发限制

989 阅读2分钟

通常,我们在需要保证代码在多个异步处理之后执行,会用到:

Promise.all(promises: []).then(
    // 后续逻辑
);

Promise.all可以保证,promises数组中所有promise对象都达到resolve状态,才执行then回调。

如果你的promises数组中每个对象都是http请求,且数量巨大,那么会出现的情况是,你在瞬间发出大量的http请求(tcp连接数不足可能造成等待),或者堆积了大量调用栈导致内存溢出。所以需要对Promise.all做并发限制。(例如微信小程序wx.request并发数超过10就会报错)

因为promise中的操作,在实例化promise对象的时候就执行了,所以只要控制promise实例化的逻辑即可。

参考async-poolasyncPool提供了两种实现,一种是基于 ES6 标准的 Promise,另外一种是利用 ES7 的 async 函数来实现

  • es6
    let result = []; // 待执行的promise数组
    console.log('开始时间:',new Date());
    for (let i = 0; i < 10; i++) {
        result.push(promiseFn(i));
    }

    PromiseLimit(result).then(res => {
        console.log('PromiseLimit成功结束时间:',new Date());
    }).catch(er => {
        console.log('PromiseLimit失败结束时间:',new Date());
    })

    // 实例化promise函数
    function promiseFn(index) {
        return function() {
          return new Promise((resolve, reject) => {
            let time = parseInt(Math.random() * 10)*1000;
            setTimeout(() => {
              console.log('第'+index+'个异步动作','耗时'+time);
              resolve(true);
            }, time);
          });
        }
    }

    // 并发限制
    function PromiseLimit(funcArray, limit = 5) {
        let i = 0;
        const result = []; // promise数组
        const executing = []; // 正在执行队列
        const queue = function() {
          // 边界处理, funcArray 为空或者 promise 都已达到 resolve 状态
          if (i === funcArray.length) return Promise.all(executing);

          // 自调用函数,初始化一个 promise
          const p = funcArray[i++]();
          // 放入 promise 数组中
          result.push(p);

          // promise 执行完毕,从正在执行队列中删除
          const e = p.then(() => executing.splice(executing.indexOf(e), 1));
          // 将正在执行的 promise 插入到 executing 数组
          executing.push(e);

          // 使用Promise.rece,每当executing数组中最早执行结束的promise完成后,实例化新的promise并执行
          if (executing.length >= limit) {
            return Promise.race(executing).then(
              () => queue(),
              e => Promise.reject(e)
            );
          }

          // 递归,直至遍历完funcArray
          return Promise.resolve().then(() => queue());
        };
        return queue().then(() => Promise.all(result));
    }

执行顺序: 从 funcArray 第 1 个元素开始,初始化 promise 对象,同时用一个 executing 数组保存正在执行的 promise 不断初始化 promise,直到达到 limit 使用 Promise.race,获得 executingpromise 的执行情况,当有一个 promise 执行完毕,继续初始化 promise 并放入 executing 中 所有 promise 都执行完了,调用 Promise.all 返回。

image.png

打印结果如图,若正常Promise.all同时执行,耗时应为10个任务中最长的那个,为9s,实现并发后,并发限制数量为5,所以该10个异步操作总耗费时间为16s。

参考链接:blog.csdn.net/ghostlpx/ar…