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来不停。

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

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

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

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

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

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

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


相关推荐
ss2734 小时前
手写MyBatis第102弹:MapperBuilder与MapperAnnotationBuilder的双重解析机制深度剖析
java·开发语言·mybatis
星光一影5 小时前
Java版旅游系统/文旅系统/旅游助手/旅游攻略/公众号/小程序/app全套源码
java·小程序·开源软件·旅游·源代码管理
计算机毕设定制辅导-无忧学长5 小时前
基于Spring Boot的酒店管理系统
java·spring boot·后端
纳于大麓5 小时前
Android Maven私服搭建(Windows)
java·maven
哈基米喜欢哈哈哈6 小时前
低版本的JVM遇到高版本的class字节码是否会报错
java·jvm
235166 小时前
【并发编程】详解volatile
java·开发语言·jvm·分布式·后端·并发编程·原理
洛小豆6 小时前
java 中 char 类型变量能不能储存一个中文的汉字,为什么?
java·后端·面试
爱吃烤鸡翅的酸菜鱼6 小时前
从数据库直连到缓存预热:城市列表查询的性能优化全流程
java·数据库·后端·spring·个人开发
一只学java的小汉堡7 小时前
Java 面试高频题:HashMap 与 ConcurrentHashMap 深度解析(含 JDK1.8 优化与线程安全原理)
java·开发语言·面试