Promise.all 可以保证,promises 数组中所有promise对象都达到 resolve 状态,才执行 then 回调。
⛲️ 场景:如果你都 promises 数组中每个对象都是http请求,你在瞬间发出几十万http请求(tcp连接数不足可能造成等待),或者堆积了无数调用栈导致内存溢出。这时,就需要考虑对Promise.all做并发限制。
Promise.all并发限制指的是,每个时刻并发执行的promise数量是固定的,最终的执行结果还是保持与原来的Promise.all一致。
- 直接使用
Promise.all
可以看到程序一执行,就输出了 1000、2000、3000、5000,多个 promise 并发执行。
var promise1 = new Promise((resolve, reject) => {
console.log(1000);
setTimeout(() => {
resolve(1000)
},1000);
})
var promise2 = new Promise((resolve, reject) => {
console.log(2000);
setTimeout(() => {
resolve(2000);
},2000);
})
var promise3 = new Promise((resolve, reject) => {
console.log(3000);
setTimeout(() => {
resolve(3000);
}, 3000);
})
var promise4 = new Promise((resolve, reject) => {
console.log(5000);
setTimeout(() => {
resolve(5000);
},5000);
})
const data = Date.now();
Promise.all([promise1, promise2, promise3, promise4]).then(value => {
console.log(value); // [ 1000, 2000, 3000, 5000 ]
console.log(Date.now() - data); //5003
});
promise 并不是因为调用 Promise.all 才执行,而是在实例化 promise 对象的时候就执行了。因此要实现并发限制,需要从promise实例化上下手。
npm中有很多实现这个功能的第三方包,比如 async-pool、es6-promise-pool、p-limit。
- 封装
asyncPllo
限制并发数为 2
function asyncPool(poolLimit, array, iteratorFn){
let i = 0;
const ret = [];
const executing = []; //保存正在执行的promise
const enqueue = function(){
// 边界处理,array为空数组
if(i === array.length){
return Promise.resolve();
}
// 每调用一次enqueue,初始化一个Promise
const item = array[i++];
const p = Promise.resolve().then(() => iteratorFn(item));
// 将初始化的promise放入promises数组
ret.push(p);
// promise执行完毕,从executing数组中删除
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
let r = Promise.resolve();
// 使用Promise.race,获得executing中promise的执行情况
// 每当正在执行的promise数量高于poolLimit,就执行一次 否则继续实例化新的Promise达到poolLimit时执行
if(executing.length >= poolLimit){
r = Promise.race(executing);
}
// 递归,直到遍历完array
return r.then(() => enqueue());
}
return enqueue().then(() => Promise.all(ret)); //所有的 promise 都执行完了,调用Promise.all返回
}
const timeout = i => new Promise(resolve => {
console.log(i);
setTimeout(() => resolve(i), i)
});
const data = Date.now();
asyncPool(2, [1000, 5000, 3000, 2000], timeout).then(results => {
console.log(results); //[1000, 2000, 3000, 5000]
console.log(Date.now() - data); //6018
})
📖 众所周知js是单线程,并不存在真正的并发,但是由于JavaScript的Event Loop机制,使得异步函数调用有了“并发”这样的假象。
它的使用场景如限制网络请求的数量,限制文件下载请求的上限等等。如微信小程序,网络请求 wx.request
、wx.downloadFile
等接口的最大并发限制是 10。
🤔️ Q:那么我们如何实现这样的功能,让我们可以随意调用受限制的函数,而又不需要当心它是否超过了限制。
😯 A:这里依然可以利用到任务队列这种思想,在每次要执行“受限”任务时,判断当前正在执行的任务数量是否超过给定的上限,如果未超过则立即执行这个“任务”,否则进入任务队列中等待执行。