147.《手写实现 Promise.all 与 Promise.race》
文章目录
- 手写实现 `Promise.all` 与 `Promise.race`
- 引言
- 实现 `myPromiseAll`:等待所有任务完成
- 核心需求分析
- 实现思路
- 代码实现
- 测试验证
- 实现 `myPromiseRace`:谁先完成就用谁
- 核心需求分析
- 实现思路
- 代码实现
- 测试验证
- 设计思想对比
- 🌰 典型应用场景
- 总结
- 上班心得
手写实现 Promise.all
与 Promise.race
摘要:
Promise.all
和Promise.race
是 JavaScript 中处理多个异步任务的常用方法。它们分别代表了“与”和“或”的并发控制逻辑。为了真正理解其内部工作机制,本文将从零开始,手写实现这两个方法。通过代码实践,深入剖析其设计思想与核心原理。
引言
在现代前端开发中,异步编程无处不在。我们经常需要同时发起多个网络请求,例如获取用户信息、商品列表和页面配置。如何高效地处理这些并发请求?
Promise.all
:等待所有异步操作完成,才进行下一步。适用于“全部成功才算成功”的场景。Promise.race
:只要任意一个异步操作完成,就立即响应。适用于“谁先完成就用谁”的竞速场景。
虽然这两个方法使用简单,但它们的内部实现却蕴含着精妙的异步控制逻辑。本文将通过手写代码,带你一步步实现 myPromiseAll
和 myPromiseRace
,揭开它们的神秘面纱。
实现 myPromiseAll
:等待所有任务完成
核心需求分析
- 接收一个 Promise 数组。
- 所有 Promise 都
resolve
时,返回一个包含所有结果的数组。 - 任一 Promise
reject
时,立即返回该错误(短路机制)。 - 结果数组的顺序应与输入数组一致。
实现思路
- 输入校验: 检查参数是否为数组且非空。
- 结果收集: 使用一个数组
result
存储每个 Promise 的返回值。 - 完成计数: 由于异步执行,无法通过同步循环判断是否全部完成。因此,引入计数器
count
,每成功一个,计数器加一。 - 触发最终 resolve: 当
count
等于数组长度时,调用resolve(result)
。 - 错误处理: 任一 Promise 失败,立即调用
reject(err)
,中断整个流程。
代码实现
function myPromiseAll(data) {// 1. 输入校验:必须是数组且非空if (!Array.isArray(data) || data.length === 0) {return Promise.resolve([]);}let result = []; // 存储每个 Promise 的结果let count = 0; // 记录已完成的 Promise 数量return new Promise((resolve, reject) => {// 遍历所有 Promisedata.forEach(prom => {// 使用 Promise.resolve 包装,确保每个元素都是 PromisePromise.resolve(prom).then(res => {result.push(res); // 收集结果count++; // 计数器加一// 当所有任务完成时,resolve 最终结果if (count === data.length) {resolve(result);}}).catch(err => {// 任一任务失败,立即 reject(短路机制)reject(err);});});});
}
测试验证
// 模拟不同耗时的 Promise
const p1 = new Promise(resolve => setTimeout(() => resolve(1), 100));
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 500));
const p3 = new Promise(resolve => setTimeout(() => resolve(3), 2000));// 测试成功情况
myPromiseAll([p1, p2, p3]).then(res => console.log('myPromiseAll res:', res)) // 输出: [1, 2, 3]// 测试失败情况(短路)
const pFail = new Promise((_, reject) => setTimeout(() => reject('出错了!'), 50));myPromiseAll([p1, pFail, p2]).catch(err => console.log('myPromiseAll err:', err)) // 输出: "出错了!",且 p2 不会再执行
实现 myPromiseRace
:谁先完成就用谁
核心需求分析
- 接收一个 Promise 数组。
- 只要有一个 Promise 完成(
resolve
或reject
),就立即返回其结果。 - 其余 Promise 的结果将被忽略。
实现思路
Promise.race
的实现相对简单:
- 并发执行: 让所有 Promise 同时开始。
- “先到先得”: 第一个调用
resolve
或reject
的 Promise,决定了最终结果。 - 立即返回: 一旦有结果,后续结果不再处理。
代码实现
function myPromiseRace(data) {// 输入校验if (!Array.isArray(data) || data.length === 0) {return Promise.resolve([]);}return new Promise((resolve, reject) => {// 遍历所有 Promise,让它们并发执行data.forEach(prom => {Promise.resolve(prom).then(res => {// 第一个成功的结果,立即 resolveresolve(res);}).catch(err => {// 第一个失败的结果,立即 rejectreject(err);});});});
}
测试验证
// 测试成功竞速
myPromiseRace([p1, p2, p3]).then(res => console.log('myPromiseRace res:', res)) // 输出: 1(p1 耗时最短)// 测试失败竞速
myPromiseRace([pFail, p1, p2]).catch(err => console.log('myPromiseRace err:', err)) // 输出: "出错了!"(pFail 最先 reject)
设计思想对比
特性 | Promise.all | Promise.race |
---|---|---|
逻辑关系 | 与(AND) | 或(OR) |
成功条件 | 所有 Promise 成功 | 任意一个 Promise 成功 |
失败条件 | 任意一个 Promise 失败 | 任意一个 Promise 失败 |
结果 | 所有结果的数组 | 第一个完成的结果 |
典型场景 | 批量数据获取 | 超时控制、资源竞速 |
🌰 典型应用场景
-
Promise.all
:const [user, posts, config] = await Promise.all([fetch('/user'),fetch('/posts'),fetch('/config') ]); // 三者都获取成功后,再渲染页面
-
Promise.race
(超时控制):function fetchWithTimeout(url, timeout) {const timer = new Promise((_, reject) => setTimeout(() => reject(new Error('Request Timeout')), timeout));return Promise.race([fetch(url), timer]); } // 请求与定时器“赛跑”,实现超时机制
总结
通过手写实现 myPromiseAll
和 myPromiseRace
,我们可以清晰地看到:
Promise.all
的核心是“计数器 + 短路”:通过计数器判断是否全部完成,并在任一失败时立即中断。Promise.race
的核心是“竞速”:所有任务并发执行,第一个返回结果的胜出。
这两个方法虽然 API 简单,但背后的设计思想非常精妙。理解其实现原理,不仅能加深对 Promise
的掌握,还能在实际开发中更灵活地运用异步控制策略。
上班心得
————————————————
上班真的好开心,需求bug来不停。
产品测试来回找,后端交互想上刑。
会议不停满楼跑,文档笔记要分清。
摸鱼睡觉一时爽,提测发布胆惊心。
大佬思绪跟得上,想法创意实践灵。
日常积累很重要,自我提高才算行。
暮然回首学生时,半载已过还未明。
处处少年何模样?如今胡须满颔停!
————————————————