1.Promise.all 和 Promise.allSettled 有什么区别?
一句话概括Promise.allSettled
和Promise.all
的最大不同:Promise.allSettled
永远不会被reject
。
🌟 Promise.all的痛点
当需要处理多个Promise并行时,大多数情况下Promise.all用起来是非常顺手的,比如下面这样
1 2 3 4 5 6 7 8 9
| const delay = n => new Promise(resolve => setTimeout(resolve, n));
const promises = [ delay(100).then(() => 1), delay(200).then(() => 2), ]
Promise.all(promises).then(values=>console.log(values))
|
可是,是一旦有一个promise出现了异常,被reject了,情况就会变的麻烦。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const promises = [ delay(100).then(() => 1), delay(200).then(() => 2), Promise.reject(3) ]
Promise.all(promises).then(values=>console.log(values))
Promise.all(promises) .then(values=>console.log(values)) .catch(err=>console.log(err))
|
尽管能用catch捕获其中的异常,但你会发现其他执行成功的Promise的消息都丢失了,仿佛石沉大海一般。
要么全部成功,要么全部重来,这是Promise.all
本身的强硬逻辑,也是痛点的来源,不能说它错,但这的确给Promise.allSettled
留下了立足的空间。
使用Promise.allSettled
处理上述代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const promises = [ delay(100).then(() => 1), delay(200).then(() => 2), Promise.reject(3) ]
Promise.allSettled(promises).then(values=>console.log(values))
|
可以看到所有promise的数据都被包含在then语句中,且每个promise的返回值多了一个status字段,表示当前promise的状态,没有任何一个promise的信息被丢失。
因此,当用Promise.allSettled时,我们只需专注在then语句里,当有promise被异常打断时,我们依然能妥善处理那些已经成功了的promise,不必全部重来。
2.实现mergePromise函数
题目描述:
实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中。
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
| const time = (timer) => { return new Promise(resolve => { setTimeout(() => { resolve() }, timer) }) } const ajax1 = () => time(2000).then(() => { console.log(1); return 1 }) const ajax2 = () => time(1000).then(() => { console.log(2); return 2 }) const ajax3 = () => time(1000).then(() => { console.log(3); return 3 })
function mergePromise () { …… }
mergePromise([ajax1, ajax2, ajax3]).then(data => { console.log("done"); console.log(data); });
|
参考答案
解题思路:
- 定义一个数组data用于保存所有异步操作的结果
- 初始化一个
const promise = Promise.resolve()
,然后循环遍历数组,在promise后面添加执行ajax任务,同时要将添加的结果重新赋值到promise上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function mergePromise (ajaxArray) { const data = []; let promise = Promise.resolve(); ajaxArray.forEach(ajax => { promise = promise.then(ajax).then(res => { data.push(res); return data; }) }) return promise; }
|
3.使用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
| function red() { console.log("red"); } function green() { console.log("green"); } function yellow() { console.log("yellow"); } const light = function (timer, cb) { return new Promise(resolve => { setTimeout(() => { cb() resolve() }, timer) }) } const step = function () { Promise.resolve().then(() => { return light(3000, red) }).then(() => { return light(2000, green) }).then(() => { return light(1000, yellow) }).then(() => { return step() }) }
step();
|
4.使用Promise实现:限制异步操作的并发个数,并尽可能快的完成全部
🌟 题目描述:
有8个图片资源的url,已经存储在数组urls中。
urls类似于[‘https://image1.png', ‘https://image2.png', ….]
而且已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。
但有一个要求,任何时刻同时下载的链接数量不可以超过3个。
请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var urls = [ "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png", "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png", ]; function loadImg(url) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = function() { console.log("一张图片加载完成"); resolve(img); }; img.onerror = function() { reject(new Error('Could not load image at' + url)); }; img.src = url; });
|
🌟参考答案:
既然题目的要求是保证每次并发请求的数量为3,那么我们可以先请求urls中的前面三个(下标为0,1,2),并且请求的时候使用Promise.race()来同时请求,三个中有一个先完成了,我们就把这个当前数组中已经完成的那一项(第1项)换成还没有请求的那一项(urls中下标为3)。
直到urls已经遍历完了,然后将最后三个没有完成的请求(也就是状态没有改变的Promise)用Promise.all()来加载它们。
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 41
| function limitLoad(urls, handler, limit) { let sequence = [].concat(urls); let promises = sequence.splice(0, limit).map((url, index) => { return handler(url).then(() => { return index; }); }); return sequence .reduce((pCollect, url) => { return pCollect .then(() => { return Promise.race(promises); }) .then(fastestIndex => { promises[fastestIndex] = handler(url).then( () => { return fastestIndex; } ); }) .catch(err => { console.error(err); }); }, Promise.resolve()) .then(() => { return Promise.all(promises); }); } limitLoad(urls, loadImg, 3) .then(res => { console.log("图片全部加载完毕"); console.log(res); }) .catch(err => { console.error(err); });
|
5.实现有并行限制的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 41 42 43 44 45 46 47
| class Scheduler { constructor() { this.queue = []; this.maxCount = 2; this.runCounts = 0; } add(promiseCreator) { this.queue.push(promiseCreator); } taskStart() { for (let i = 0; i < this.maxCount; i++) { this.request(); } } request() { if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) { return; } this.runCounts++;
this.queue.shift()().then(() => { this.runCounts--; this.request(); }); } } const timeout = time => new Promise(resolve => { setTimeout(resolve, time); }) const scheduler = new Scheduler(); const addTask = (time,order) => { scheduler.add(() => timeout(time).then(()=>console.log(order))) } addTask(1000, '1'); addTask(500, '2'); addTask(300, '3'); addTask(400, '4'); scheduler.taskStart()
|
6.promisify
🌰 适用场景:
- 原生回调函数 callBack:存在回调地狱问题
- 函数加上返回 Promise 对象构造成异步函数:每次都需要重新构造,重复工作
- 使用 Promisify 做 Promise 的封装:减少构造 Promise 的重复工作
1 2 3 4 5 6 7 8 9 10 11 12 13
| function promisify(func) { return function (...args) { return new Promise( (resolve, reject) => { let callback = function(...args) { resolve(args) } func.apply(null, [...args, callback]) }) } }
|
验证:
1 2 3 4 5 6 7 8 9 10 11
| function hello(v){ setTimeout(()=>{ console.log(v+1) },1000) }
hello(2)
var newFn = new promisify(hello) newFn(2).then((v)=>console.log(v))
|
结果: