147.《手写实现 Promise.all 与 Promise.race》

文章目录

手写实现 Promise.allPromise.race

摘要: Promise.allPromise.race 是 JavaScript 中处理多个异步任务的常用方法。它们分别代表了"与"和"或"的并发控制逻辑。为了真正理解其内部工作机制,本文将从零开始,手写实现这两个方法。通过代码实践,深入剖析其设计思想与核心原理。


引言

在现代前端开发中,异步编程无处不在。我们经常需要同时发起多个网络请求,例如获取用户信息、商品列表和页面配置。如何高效地处理这些并发请求?

  • Promise.all :等待所有异步操作完成,才进行下一步。适用于"全部成功才算成功"的场景。
  • Promise.race :只要任意一个异步操作完成,就立即响应。适用于"谁先完成就用谁"的竞速场景。

虽然这两个方法使用简单,但它们的内部实现却蕴含着精妙的异步控制逻辑。本文将通过手写代码,带你一步步实现 myPromiseAllmyPromiseRace,揭开它们的神秘面纱。


实现 myPromiseAll:等待所有任务完成

核心需求分析

  • 接收一个 Promise 数组。
  • 所有 Promise 都 resolve 时,返回一个包含所有结果的数组。
  • 任一 Promise reject 时,立即返回该错误(短路机制)。
  • 结果数组的顺序应与输入数组一致。

实现思路

  1. 输入校验: 检查参数是否为数组且非空。
  2. 结果收集: 使用一个数组 result 存储每个 Promise 的返回值。
  3. 完成计数: 由于异步执行,无法通过同步循环判断是否全部完成。因此,引入计数器 count,每成功一个,计数器加一。
  4. 触发最终 resolve:count 等于数组长度时,调用 resolve(result)
  5. 错误处理: 任一 Promise 失败,立即调用 reject(err),中断整个流程。

代码实现

javascript 复制代码
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) => {
        // 遍历所有 Promise
        data.forEach(prom => {
            // 使用 Promise.resolve 包装,确保每个元素都是 Promise
            Promise.resolve(prom)
                .then(res => {
                    result.push(res); // 收集结果
                    count++; // 计数器加一
                    // 当所有任务完成时,resolve 最终结果
                    if (count === data.length) {
                        resolve(result);
                    }
                })
                .catch(err => {
                    // 任一任务失败,立即 reject(短路机制)
                    reject(err);
                });
        });
    });
}

测试验证

javascript 复制代码
// 模拟不同耗时的 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 完成(resolvereject),就立即返回其结果。
  • 其余 Promise 的结果将被忽略。

实现思路

Promise.race 的实现相对简单:

  1. 并发执行: 让所有 Promise 同时开始。
  2. "先到先得": 第一个调用 resolvereject 的 Promise,决定了最终结果。
  3. 立即返回: 一旦有结果,后续结果不再处理。

代码实现

javascript 复制代码
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 => {
                    // 第一个成功的结果,立即 resolve
                    resolve(res);
                })
                .catch(err => {
                    // 第一个失败的结果,立即 reject
                    reject(err);
                });
        });
    });
}

测试验证

javascript 复制代码
// 测试成功竞速
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

    javascript 复制代码
    const [user, posts, config] = await Promise.all([
        fetch('/user'),
        fetch('/posts'),
        fetch('/config')
    ]);
    // 三者都获取成功后,再渲染页面
  • Promise.race(超时控制):

    javascript 复制代码
    function fetchWithTimeout(url, timeout) {
        const timer = new Promise((_, reject) => 
            setTimeout(() => reject(new Error('Request Timeout')), timeout)
        );
        return Promise.race([fetch(url), timer]);
    }
    // 请求与定时器"赛跑",实现超时机制

总结

通过手写实现 myPromiseAllmyPromiseRace,我们可以清晰地看到:

  • Promise.all 的核心是"计数器 + 短路":通过计数器判断是否全部完成,并在任一失败时立即中断。
  • Promise.race 的核心是"竞速":所有任务并发执行,第一个返回结果的胜出。

这两个方法虽然 API 简单,但背后的设计思想非常精妙。理解其实现原理,不仅能加深对 Promise 的掌握,还能在实际开发中更灵活地运用异步控制策略。

上班心得


上班真的好开心,需求bug来不停。

产品测试来回找,后端交互想上刑。

会议不停满楼跑,文档笔记要分清。

摸鱼睡觉一时爽,提测发布胆惊心。

大佬思绪跟得上,想法创意实践灵。

日常积累很重要,自我提高才算行。

暮然回首学生时,半载已过还未明。

处处少年何模样?如今胡须满颔停!


相关推荐
半旧夜夏43 分钟前
【分布式缓存】Redis持久化和集群部署攻略
java·运维·redis·分布式·缓存
短视频矩阵源码定制1 小时前
矩阵系统源码推荐:技术架构与功能完备性深度解析
java·人工智能·矩阵·架构
Eiceblue1 小时前
使用 Java 将 Excel 工作表转换为 CSV 格式
java·intellij-idea·excel·myeclipse
漂流幻境1 小时前
IntelliJ IDEA的Terminal中执行ping命令时遇到的“No route to host“问题
java·ide·intellij-idea
苹果醋31 小时前
element-ui源码阅读-样式
java·运维·spring boot·mysql·nginx
BUG?不,是彩蛋!1 小时前
IntelliJ IDEA从安装到使用:零基础完整指南
java·ide·intellij-idea
程序员阿鹏2 小时前
56.合并区间
java·数据结构·算法·leetcode
SmoothSailingT2 小时前
IDEA实用快捷键
java·ide·intellij-idea
rengang662 小时前
Spring AI Alibaba 框架使用示例总体介绍
java·人工智能·spring·spring ai·ai应用编程
没有bug.的程序员2 小时前
@Controller、@RestController、@RequestMapping 解析机制
java·spring boot·spring·controller·requestmapping·restcontroller