阅读 64

关于Promise.all的一点思考

首先看阮一峰老师的《ECMAScript 6 入门教程》中的这样一段:

image.png

所以一旦有一个promise状态变为了reject,Promise.all立即结束,且Promise.all只获取到(第一个)reject的实例的返回值,其他的一概丢失。

为解决这个“缺陷”,ES2020引入了Promise.allSettled方法,使用方式与Promise.all如出一辙,但其会包含所有Promise实例的返回值,无论状态是fulfilled还是rejected:

// 生成promise的函数数组
const array = [];
for (let index = 0; index < 6; index++) {
  array.push(function () {
    return new Promise((resolve, reject) => {
      console.log('开始' + index);
      setTimeout(() => {
        index !== 1 ? resolve(index) : reject(index);
        console.log('结束' + index);
      }, 1000);
    });
  });
}

// 调用Promise.allSettled
Promise.allSettled(array.map(item => item()))
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.log(err);
  });
复制代码

打印结果:

1.png

不过,既然是ES2020引入的,必然有兼容性问题(谷歌80.xxx已支持,其他第三方浏览器还未支持),所以可能需要polyfill一下:

// 前置知识点:then方法返回一个新的Promise实例,可以采用链式写法
// 第一个then中无论执行fulfilled还是rejected,第二个then中都会执行fulfilled
Promise.resolve('success')
  .then(
    res => res,
    err => err
  )
  .then(res => console.log(res)); // 执行,打印success

Promise.reject('fail')
  .then(
    res => res,
    err => err
  )
  .then(res => console.log(res)); // 也会执行,打印fail

// polyfill
Promise.allSettled2 = function (array) {
  function processPromise(promiseList) {
    return promiseList.map(item =>
      item.then(
        value => ({ status: 'ok', value }),
        reason => ({ status: 'error', reason })
      )
    );
  }
  return Promise.all(processPromise(array));
};
复制代码

打印结果:

2.png

如果Promise.all的参数,promise数组中有几十个promise,每个promise都是发送几十个http请求,就必然会发生性能问题,所以这时需要考虑做并发控制。(的确,一般的项目需求不会有这样的情况发生,但考虑到大文件切片上传这样的场景)

目前npm中已经有了一些成熟的解决工具,例如tiny-async-pool、es6-promise-pool、p-limit等。其本质基本类似,都是建立一个队列(数组),限制并发数,执行成功一个,就从队列里弹出一个,再向队列里添加一个新的,这里实现一个简单示例:

// 假设限制并发数为3
function PromiseLimit(tasks, limit = 3) {
  return new Promise((resolve, reject) => {
    const len = tasks.length;
    let count = 0;
    const result = [];
    function start() {
      const task = tasks.shift();
      if (task) {
        task().then(
          res => {
            if (count === len - 1) {
              resolve(result.concat(res));
            } else {
              count += 1;
              result.push(res);
              start();
            }
          },
          err => {
            // 这里可以进行优化,例如至多重试3次
            console.warn('重试');
            tasks.unshift(task);
            start();
          }
        );
      }
    }
    while (limit > 0) {
      start();
      limit -= 1;
    }
  });
}

// 调用
PromiseLimit(array).then(
  res => {
    console.log(res);
  },
  err => {
    console.log(err);
  }
);
复制代码

3.png 4.png

共勉。

文章分类
前端
文章标签