# 手写Promise.all和Promise.race,面试再也不怕了

Promise是JS异步编程的核心,Promise.all和Promise.race更是面试常考的手写题。

今天从零实现这两个方法,顺便理解Promise的设计哲学。

Promise.all:全部成功才成功

Promise.all接收一个Promise数组,所有都fulfilled才返回结果数组,任意一个rejected就失败。

javascript 复制代码
// 用法
const results = await Promise.all([
    fetchUser(1),
    fetchOrders(1),
    fetchConfig()
]);
// results = [user, orders, config]

手写实现:

javascript 复制代码
function promiseAll(promises) {
    return new Promise((resolve, reject) => {
        // 不是数组,报错
        if (!Array.isArray(promises)) {
            return reject(new TypeError('Argument must be an array'));
        }

        const results = [];
        let resolvedCount = 0;
        const total = promises.length;

        // 空数组直接resolve
        if (total === 0) {
            return resolve([]);
        }

        promises.forEach((promise, index) => {
            // 包装非Promise值
            Promise.resolve(promise).then(
                (value) => {
                    results[index] = value; // 注意:用index保证顺序
                    resolvedCount++;

                    if (resolvedCount === total) {
                        resolve(results);
                    }
                },
                (reason) => {
                    reject(reason); // 任意一个失败就reject
                }
            );
        });
    });
}

// 测试
promiseAll([
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3)
]).then(console.log); // [1, 2, 3]

promiseAll([
    Promise.resolve(1),
    Promise.reject('error'),
    Promise.resolve(3)
]).catch(console.log); // 'error'

关键点:

  1. 结果数组的顺序和输入顺序一致(不是按完成顺序)
  2. 任意一个rejected就立即reject
  3. 非Promise值会被Promise.resolve包装

Promise.race:谁快用谁

Promise.race返回最先完成的那个Promise的结果,不管成功还是失败。

javascript 复制代码
// 用法:超时控制
const result = await Promise.race([
    fetchData(),
    new Promise((_, reject) =>
        setTimeout(() => reject(new Error('timeout')), 5000)
    )
]);

手写实现:

javascript 复制代码
function promiseRace(promises) {
    return new Promise((resolve, reject) => {
        for (const promise of promises) {
            Promise.resolve(promise).then(
                (value) => resolve(value),  // 第一个完成的直接resolve
                (reason) => reject(reason)  // 第一个失败的直接reject
            );
        }
    });
}

// 测试:超时控制
function fetchWithTimeout(url, timeout = 5000) {
    return Promise.race([
        fetch(url),
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('请求超时')), timeout)
        )
    ]);
}

Promise.allSettled:等全部完成

Promise.allSettled等所有Promise都完成(不管成败),返回每个的状态和结果。

javascript 复制代码
const results = await Promise.allSettled([
    Promise.resolve('成功'),
    Promise.reject('失败'),
    Promise.resolve('又成功')
]);

// [
//   { status: 'fulfilled', value: '成功' },
//   { status: 'rejected', reason: '失败' },
//   { status: 'fulfilled', value: '又成功' }
// ]

手写实现:

javascript 复制代码
function promiseAllSettled(promises) {
    return new Promise((resolve) => {
        const results = [];
        let completed = 0;
        const total = promises.length;

        if (total === 0) return resolve([]);

        promises.forEach((promise, index) => {
            Promise.resolve(promise).then(
                (value) => {
                    results[index] = { status: 'fulfilled', value };
                },
                (reason) => {
                    results[index] = { status: 'rejected', reason };
                }
            ).finally(() => {
                completed++;
                if (completed === total) {
                    resolve(results);
                }
            });
        });
    });
}

Promise.any:第一个成功的

Promise.any返回第一个成功的Promise,全部失败才reject。

javascript 复制代码
function promiseAny(promises) {
    return new Promise((resolve, reject) => {
        const errors = [];
        let rejectedCount = 0;
        const total = promises.length;

        if (total === 0) {
            return reject(new AggregateError([], 'All promises were rejected'));
        }

        promises.forEach((promise, index) => {
            Promise.resolve(promise).then(
                (value) => resolve(value),
                (reason) => {
                    errors[index] = reason;
                    rejectedCount++;
                    if (rejectedCount === total) {
                        reject(new AggregateError(errors, 'All promises were rejected'));
                    }
                }
            );
        });
    });
}

四个方法对比

方法 成功条件 失败条件 返回
Promise.all 全部成功 任意一个失败 结果数组
Promise.race 第一个完成 第一个失败 单个结果
Promise.allSettled 全部完成(不管成败) 不会失败 状态+结果数组
Promise.any 任意一个成功 全部失败 单个结果

手写Promise系列是面试高频题,核心就是理解Promise的构造和状态管理。把这几个实现搞懂,Promise就不再是难点了。

建议自己在编辑器里写一遍,比看十遍都管用。

相关推荐
ZhengEnCi5 小时前
工科读研真的能抗学历贬值吗:研究生 vs 本科生的真实差距与选型判断
面试
Rain的Java大神之路6 小时前
国家数据集团校招、社招都开了:总部雄安,这些技术岗值得认真看看
java·后端·面试
Moment6 小时前
为什么这么多 Agent Harness 都选择 TypeScript
前端·后端·面试
时间的拾荒人7 小时前
CMake 实战指南:从基础语法到静态库/动态库制作与链接
开发语言·c++·面试
翊众建站18 小时前
用 JSON-LD 给企业官网做结构化数据:LocalBusiness 与 FAQPage 实践笔记
人工智能·搜索引擎·json
IT规划师1 天前
编程术语中的同步与异步:从"等不等"到工程取舍
后端·面试
Patrick_Wilson1 天前
读懂一个 Go JSON 处理函数:从输入数据追踪结构体、校验与错误返回
go·json·编程语言
考研保研资料分享1 天前
2022环境工程保研北师大:三校专业追问、论文贡献与择校复盘
面试
Rain的Java大神之路1 天前
面试官让你把 LLM、RAG、Agent、MCP 串起来讲清楚,90% 的 Java 工程师当场露馅
java·后端·面试
狂师1 天前
我开发了一款免费开源的测试开发刷题学习网站
面试·开源·测试