实现多个promise串行执行
在 async/await 以前 Promise 串行执行还是比较麻烦的,除了依赖 async、promise-fun 等工具库,我们还可以使用 Array.prototype.reduce() 自己实现一个简单的串行Promise了:
1 2 3 4 5 6 7 8 9
|
function runPromiseByQueue(myPromises) { myPromises.reduce( (previousPromise, nextPromise) => previousPromise.then(() => nextPromise()), Promise.resolve() ); }
|
当上一个 Promise 开始执行(previousPromise.then),当其执行完毕后再调用下一个 Promise,并作为一个新 Promise 返回,下次迭代就会继续这个循环。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const createPromise = (time, id) => () => new Promise((solve) => { console.time("time" + id); setTimeout(() => { console.log("promise", id); console.timeEnd("time" + id); if (id == 3) { console.timeEnd("timeall"); } solve(); }, time); });
console.time("timeall"); runPromiseByQueue([ createPromise(3000, 1), createPromise(2000, 2), createPromise(1000, 3), ]);
|
最后的输出结果如下:

说明确实是串行依次执行了三个promise。
实现多个promise并行执行
这个目前ES官方已经实现了promise.all/promise.allSettled/promise.race等方法,以promise.all()为例:
1 2 3 4 5 6 7 8 9 10
| const promise1 = Promise.resolve(3); const promise2 = 42; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); });
Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); });
|
Promise.all
可以保证,promises
数组中所有promise对象都达到resolve
状态,才执行then
回调。
那么如果promises
数组是包含几十个甚至几百个http请求,直接用Promise.all
的话,会瞬间发出所有的http请求,造成请求拥堵甚至失败。
这时候就需要对Promise.all
做并发限制。
promise.all并行限制
首先需要明白的是,promise并不是在调用Promise.all才执行,而是在实例化promise对象的时候就执行了,在理解这一点的基础上,就可以从promise实例化上下手实现并发限制。具体实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
function LimitPromiseAll(array, poolLimit) { let i = 0; const results = []; const executing = []; const enqueue = function () { if (i === array.length) { return Promise.resolve(); } const fn = array[i++]; const p = Promise.resolve().then(() => fn()); results.push(p); const e = p.then((res) => { executing.splice(executing.indexOf(e), 1); }); executing.push(e);
let r = Promise.resolve(); if (executing.length >= poolLimit) { r = Promise.race(executing); } return r.then(() => enqueue()); };
return enqueue().then(() => Promise.all(results)); }
|
使用例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| console.time("all-time"); const timeout = (i) => new Promise((resolve) => setTimeout(() => resolve(i), i));
LimitPromiseAll( [ timeout.bind(null, 1000), timeout.bind(null, 5000), timeout.bind(null, 2000), timeout.bind(null, 4000), ], 2 ).then((res) => { console.log(res); console.timeEnd("all-time"); });
|
其实,目前社区里已经有一些开源包实现了这个功能,比如async-pool、es6-promise-pool、p-limit。上述实现代码也是参考async-pool的实现。
使用Promise实现fetch超时处理
因为fetch
默认没有请求超时设置,以及中断请求操作,在这里我们尝试使用Promise
结合AbortController封装一个简单的请求超时和中断请求的fetch
。
AbortController接口代表一个控制器对象,允许你在需要时中止一个或多个DOM请求
目前IE外的主流浏览器基本都实现了AbortController功能;
AbortController的浏览器兼容情况:

核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| function TimeoutFetch(params) { this.controller = new AbortController(); this.signal = this.controller.signal; this.timeout = 5000; }
TimeoutFetch.prototype.setTimeout = function (ms) { this.timeout = ms; };
TimeoutFetch.prototype.fetch = function (url, data = {}) { const timeoutPromise = new Promise((resolve) => { setTimeout(() => { resolve({ code: 1, msg: `timeout of ${this.timeout}ms`, }); this.controller.abort(); }, this.timeout); }); const fetchPromise = fetch(url, { signal: this.signal, ...data, }); return Promise.race([fetchPromise, timeoutPromise]); };
TimeoutFetch.prototype.abort = function () { this.controller.abort(); };
|
使用例子:
1 2 3 4 5
| const timeoutFetch = new TimeoutFetch(); timeoutFetch.setTimeout(30); timeoutFetch.fetch("http://localhost/data").then((res) => { console.log(res); });
|