算法 - 手写 Promise.all
1. 题目
实现一个函数 promiseAll(iterables),行为对齐原生 Promise.all:
- 接收一个(Promise 或普通值可迭代)数组,返回一个新 Promise。
- 全部成功时,新 Promise 以「与输入顺序一致」的结果数组 resolve。
- 任意一个失败时,立即 reject 第一个失败的原因(快速失败)。
- 要能处理非 Promise 元素(普通值直接当作已成功)。
- 空数组应立即 resolve 为
[]。
示例:
1 | promiseAll([1, Promise.resolve(2), fetchUrl]) |
2. 解题思路
三个关键点:
- 顺序保持:结果数组按输入下标写入,不能用
push(谁先完成谁 push 会乱序)。给每个任务捕获其index,完成时results[index] = value。 - 完成计数:用一个计数器
completed,每当一个任务成功就+1;当completed === 总长度时resolve(results)。因为异步完成顺序不定,只能靠计数判断「全好了」。 - 快速失败:在任一任务
catch到错误时,直接reject(reason)。Promise 一旦 settle 就冻结,后续再 reject 无效,所以无需额外加锁,天然只 reject 第一次。
用 Promise.resolve(x).then(...) 包裹每个元素,可同时兼容「普通值」和「Promise」——普通值会被包装成已完成的 Promise。
- 时间复杂度:
O(n)。
3. TypeScript 实现
1 | function promiseAll<T>( |
4. 面试延伸
手写 Promise 家族常一起问,趁热记住区别:
Promise.allSettled:等全部 settle(不管成败),返回{status, value/reason}数组,永不 reject。Promise.race:第一个 settle(含 reject)的结果定胜负。Promise.any:第一个成功的结果;全部失败才 reject 一个AggregateError。- 并发控制(重点):真实场景不能无脑并发几千个请求。进阶手写
promiseAllLimit(tasks, limit),用「任务池 / 索引指针」维持最多limit个在跑,是字节/腾讯高频加试题。 - 别忘了答「为什么按 index 写而不是 push」——这几乎是本题的核心得分点。
难度:中等 | 前端手写 Promise 必考 | 异步并发基石