字节飞书面试 - 请实现 Promise.all
何为 Promise.all?
Promise.all
是 es6 Promise
对象上的一个方法,它的功能就是将多个 Promise
实例包装成一个 promise
实例。以下是 MDN 对 Promise.all
的描述:
Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个`Promise`[1]实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个`Promise`[2]的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。
我戴上我的 300 度近视眼镜,仔细地提取出这段描述中的关键字:
Promise.all
的返回值是一个新的Promise
实例。Promise.all
接受一个可遍历的数据容器,容器中每个元素都应是Promise
实例。咱就是说,假设这个容器就是数组。- 数组中每个
Promise
实例都成功时(由pendding
状态转化为fulfilled
状态),Promise.all
才成功。这些Promise
实例所有的resolve
结果回按原来的顺序集合在一个数组中作为Promise.all
的resolve
的结果。 - 数组中只要有一个
Promise
实例失败(由pendding
状态转化为rejected
状态),Promise.all
就失败。Promise.all
的.catch()
会捕获到这个reject
。
原生 Promise.all 测试
咱先看看原生的 Promise.all
的是啥效果。
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延时一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延时两秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延时 1.5 秒')
}, 1500)
})
// 所有 Promise 实例都成功
Promise.all([p1, p2, p3])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // 2 秒后打印 [ 'p1', 'p2 延时一秒', 'p3 延时两秒' ]
// 一个 Promise 实例失败
Promise.all([p1, p2, p4])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // p4 rejected
// 一个延时失败的 Promise
Promise.all([p1, p2, p5])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // 1.5 秒后打印 p5 rejected
// 两个 Promise 实例失败
Promise.all([p1, p4, p5])
.then(res => {
console.log(res)
})
.catch(err => console.log(err)) // p4 rejected
注意
上面 p4
和 p5
在未传入 Promise.all
时需要注释掉,因为一个调用了 reject
的 Promise
实例如果没有使用 .catch()
方法去捕获错误会报错。但如果 Promise
实例定义了自己的 .catch
,就不会触发 Promise.all
的 .catch()
方法。
OK,理论存在,实践开始!
手动实现 Promise.all
Promise.all 接受一个数组,返回值是一个新的 Promise 实例
Promise.MyAll = function (promises) {
return new Promise((resolve, reject) => {
})
}
数组中所有 Promise
实例都成功, Promise.all
才成功。不难想到,咱得需要一个数组来收集这些 Promise
实例的 resolve
结果。但有句俗话说得好:“不怕一万,就怕万一”,万一数组里面有元素不是 Promise
咋办 —— 那就得用 Promise.resolve()
把它办了。这里还有一个问题, Promise
实例是不能直接调用 resolve
方法的,咱得在 .then()
中去收集结果。注意要保持结果的顺序。
Promise.MyAll = function (promises) {
let arr = []
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
})
})
})
}
将收集到的结果(数组 arr
)作为参数传给外层的 resolve
方法。这里咱们肯定是有一个判断条件的,如何判断所有 Promise
实例都成功了呢?新手容易写出这句代码:
if (arr.length === promises.length) resolve(arr)
咱仔细想想 Promise
使用来干嘛的 —— 处理异步任务。对呀,异步任务很多都需要花时间呀,如果这些 Promise
中最后一个先完成呢?那 arr
数组不就只有最后一项了,前面的所有项都是 empty
。所以这里咱们应该创建一个计数器,每有一个 Promise
实例成功,计数器加一:
Promise.MyAll = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
count += 1
if (count === promises.length) resolve(arr)
})
})
})
}
最后就是处理失败的情况了,这里有两种写法,第一种是用 .catch()
方法捕获失败:
Promise.MyAll = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
count += 1
if (count === promises.length) resolve(arr)
}).catch(reject)
})
})
}
第二种写法就是给 .then()
方法传入第二个参数,这个函数是处理错误的回调函数:
Promise.MyAll = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(res => {
arr[i] = res
count += 1
if (count === promises.length) resolve(arr)
}, reject)
})
})
}
测试案例
致此 Promise.all
大功告成,赶紧拿来测试一下(摩拳擦掌):
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延时一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延时两秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延时 1.5 秒')
}, 1500)
})
// 所有 Promsie 都成功
Promise.MyAll([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // 2 秒后打印 [ 'p1', 'p2 延时一秒', 'p3 延时两秒' ]
// 一个 Promise 失败
Promise.MyAll([p1, p2, p4])
.then(res => console.log(res))
.catch(err => console.log(err)) // p4 rejected
// 一个延时失败的 Promise
Promise.MyAll([p1, p2, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // 1.5 秒后打印 p5 rejected 延时 1.5 秒
// 两个失败的 Promise
Promise.MyAll([p1, p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // p4 rejected
与原生的 Promise.all
运行结果不能说很像,只能说一模一样。老话说的好,趁热打铁——正在火候上。我打开某个学习网站(MDN Web Docs \(mozilla.org\)[3]),了解到 Promise
对象用于同时处理多个 Promise
的方法还有 Promise.race
、 Promise.any
、 Promise.allSettle
。从小老师就教会了咱们举一反三,仔细看了这三个方法的描述之后,我还真给反出来了。
Promise.race
Promise.race
从字面意思理解就是赛跑,以状态变化最快的那个 Promise
实例为准,最快的 Promise
成功 Promise.race
就成功,最快的 Promise
失败 Promise.race
就失败。
咱来看看原生 Promise.race
效果
原生 Promise.race 测试
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延时一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延时两秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延时 1 秒')
}, 1500)
})
// p1 无延时,p2 延时 1s,p3 延时 2s
Promise.race([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p1
// p4 无延时 reject
Promise.race([p4, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p4 rejected
// p5 延时 1.5 秒 reject,p2 延时 1s
Promise.race([p5, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // 1s 后打印: p2 延时一秒
理论存在,实践开始
手写 Promise.race
整体流程与 Promise
差不多,只是对数组中的 Promise
实例处理的逻辑不一样,这里我们需要将最快改变状态的 Promise
结果作为 Promise.race
的结果,相对来说就比较简单了,代码如下:
Promise.MyRace = function (promises) {
return new Promise((resolve, reject) => {
// 这里不需要使用索引,只要能循环出每一项就行
for (const item of promises) {
Promise.resolve(item).then(resolve, reject)
}
})
}
测试案例
还是刚才几个案例,咱就不重复写了
// p1 无延时,p2 延时 1s,p3 延时 2s
Promise.MyRace([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p1
// p4 无延时 reject
Promise.MyRace([p4, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p4 rejected
// p5 延时 1.5 秒 reject,p2 延时 1s
Promise.MyRace([p5, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // 1s 后打印: p2 延时一秒
可以看到,结果与原生的 Promise.race
是一致的,成功!
Promise.any
Promise.any
与 Promise.all
可以看做是相反的。 Promise.any
中只要有一个 Promise
实例成功就成功,只有当所有的 Promise
实例失败时 Promise.any
才失败,此时 Promise.any
会把所有的失败/错误集合在一起,返回一个失败的 promise
和`AggregateError`[4]类型的实例。MDN 上说这个方法还处于试验阶段,如果 node
或者浏览器版本过低可能无法使用,各位看官自行测试下。
原生 Promise.any 测试
const p1 = Promise.resolve('p1')
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延时一秒')
}, 1000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延时两秒')
}, 2000)
})
const p4 = Promise.reject('p4 rejected')
const p5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延时 1.5 秒')
}, 1500)
})
// 所有 Promise 都成功
Promise.any([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err)) // p1
// 两个 Promise 成功
Promise.any([p1, p2, p4])
.then(res => console.log(res))
.catch(err => console.log(err)) // p1
// 只有一个延时成功的 Promise
Promise.any([p2, p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // p2 延时 1 秒
// 所有 Promise 都失败
Promise.any([p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err)) // AggregateError: All promises were rejected
可以看出,如果 Promise.any
中有多个成功的 Promise
实例,则以最快成功的那个结果作为自身 resolve
的结果。
OK,理论存在,实践开始
手写 Promise.any
依葫芦画瓢,咱们先写出 `Promise.any` 的整体结构:
Promise.MyAny = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
})
})
}
这里跟`Promise.all` 的逻辑是反的,咱们需要收集 `reject` 的 `Promise`,也需要一个数组和计数器,用计数器判断是否所有的 `Promise` 实例都失败。另外在收集失败的 `Promise` 结果时咱需要打上一个失败的标记方便分析结果。
Promise.MyAny = function (promises) {
let arr = [],
count = 0
return new Promise((resolve, reject) => {
promises.forEach((item, i) => {
Promise.resolve(item).then(resolve, err => {
arr[i] = { status: 'rejected', val: err }
count += 1
if (count === promises.length) reject(new Error('没有 promise 成功'))
})
})
})
}
这里我没有使用 MDN 上规定的 AggregateError
实例,手写嘛,随心所欲一点,写自己看着舒服的
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论