主题
Promise.all 手写
手写 Promise.all 的核心不是把代码背下来,而是先理解它到底要满足什么行为。
Promise.all 的目标
可以先记成三条规则:
- 所有项都成功,整体才成功
- 只要有一项失败,整体立刻失败
- 成功结果的顺序要和输入顺序一致
js
Promise.all([taskA(), taskB(), taskC()])这里即使 taskB 比 taskA 先完成,最终结果数组的位置也不能乱。
手写时最关键的点
主要有四个:
- 返回一个新的 Promise
- 兼容普通值和 Promise
- 记录每一项的原始索引
- 统计还有多少项没完成
一种常见实现
js
Promise.myAll = function (iterable) {
return new Promise((resolve, reject) => {
const items = Array.from(iterable)
const results = []
if (items.length === 0) {
resolve([])
return
}
let remaining = items.length
items.forEach((item, index) => {
Promise.resolve(item).then(
(value) => {
results[index] = value
remaining -= 1
if (remaining === 0) {
resolve(results)
}
},
(error) => {
reject(error)
},
)
})
})
}为什么要用 Promise.resolve
因为 Promise.all 不只接收 Promise,也接收普通值:
js
Promise.all([1, Promise.resolve(2), 3])所以手写时通常要先统一包装:
js
Promise.resolve(item)这样普通值会被转成已成功的 Promise。
为什么要记录索引
如果直接按“谁先完成就 push 到数组里”,结果顺序会错。
js
results[index] = value这一步是为了保证输出顺序和输入顺序一致,而不是按完成顺序排列。
为什么要统计剩余数量
因为只有当全部都成功结束时,才能整体 resolve:
js
let remaining = items.length每成功一个就减一,减到 0 才说明全部完成。
为什么失败时可以直接 reject
因为原生 Promise.all 的行为就是“有一个失败就整体失败”。
js
reject(error)之后即使其他 Promise 还在继续完成,也不会再改变整体结果。
空数组是个边界情况
js
Promise.all([])它应该直接成功,并返回空数组:
js
resolve([])这也是手写实现里常见的漏点。
这类题真正想考什么
通常不是考规范细节,而是考是否理解:
- Promise 聚合模型
- 顺序保持
- 普通值兼容
- 成功和失败分支
使用建议
- 手写前先把行为规则写清楚,再开始实现。
- 遇到“结果顺序错了”的问题,先检查是不是直接
push了。 - 遇到“普通值不兼容”的问题,先想到
Promise.resolve()。 - 面试手写能把核心行为实现对,通常就已经够用了。
