标准答案
- 入参通常是一个可迭代对象,里面可以是 Promise,也可以是普通值。
- 返回值必须是一个新的 Promise。
- 所有任务应该并发开始,而不是一个完成后再启动下一个。
- 结果数组要按入参顺序保存,不能按完成顺序保存。
- 任意一个任务 reject,外层 Promise 就 reject;如果入参为空,应立即 resolve 一个空数组。
题目解析
Promise.all 容易写错的地方在“并发”和“顺序”同时存在。任务完成顺序是不确定的,但返回数组必须保持输入顺序,所以每个任务都要记住自己的 index。
它不是错误收集工具。只要有一个任务失败,外层就进入 rejected;如果业务要知道每个任务成功还是失败,应该考虑 allSettled 或自己封装结果结构。
手写时还要把普通值包成 Promise.resolve(value),这样入参里混合 Promise 和普通值也能得到一致行为。
代码示例
下面是一个覆盖顺序、空数组和快速失败的简化实现。
JavaScript
function promiseAll(items) {
return new Promise((resolve, reject) => {
const list = Array.from(items)
const results = []
let fulfilledCount = 0
if (list.length === 0) {
resolve([])
return
}
list.forEach((item, index) => {
Promise.resolve(item).then(
value => {
results[index] = value
fulfilledCount += 1
if (fulfilledCount === list.length) {
resolve(results)
}
},
reject
)
})
})
}常见误区
- 用 push 保存结果,导致返回顺序变成完成顺序。
- 忘记处理空数组,外层 Promise 永远不 resolve。
- 把任务写成串行 await,失去 Promise.all 的并发意义。