关于Promise.all的一点思考
分类:javascript
首先看阮一峰老师的《ECMAScript 6 入门教程》中的这样一段:
所以一旦有一个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);
});
打印结果:
不过,既然是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));
};
打印结果:
如果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);
}
);
共勉。