跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.js大前端算法

2721. 并行执行异步函数

综述由AI生成如何在 JavaScript 中不使用内置的 Promise.all() 方法,手动实现并行执行异步函数的功能。核心逻辑是创建一个新 Promise,遍历输入函数数组,同时启动所有异步任务。若所有任务成功,则按顺序返回结果数组;若任一任务失败,则立即拒绝并返回错误原因。文章提供了基于 async/await 和 then/catch 两种语法的实现方案,并分析了时间复杂度 O(N) 和空间复杂度 O(N)。

战神发布于 2026/3/22更新于 2026/6/229 浏览
2721. 并行执行异步函数

2721. 并行执行异步函数

一、题目

给定一个异步函数数组 functions,返回一个新的 promise 对象 promise。数组中的每个函数都不接受参数并返回一个 promise。所有的 promise 都应该并行执行。

promise resolve 条件:

  • 当所有从 functions 返回的 promise 都成功的并行解析时。promise 的解析值应该是一个按照它们在 functions 中的顺序排列的 promise 的解析值数组。promise 应该在数组中的所有异步函数并行执行完成时解析。

promise reject 条件:

  • 当任何从 functions 返回的 promise 被拒绝时。promise 也会被拒绝,并返回第一个拒绝的原因。

请在不使用内置的 Promise.all 函数的情况下解决。

示例 1:

输入:functions = [ () => new Promise(resolve => setTimeout(() => resolve(5), 200)) ]
输出:{"t": 200, "resolved": [5]}
解释:promiseAll(functions).then(console.log); // [5] 单个函数在 200 毫秒后以值 5 成功解析。

示例 2:

输入:functions = [ () => new Promise(resolve => setTimeout(() => resolve(1), 200)), () => new Promise((resolve, reject) => setTimeout(() => reject("Error"), 100)) ]
输出:{"t": 100, "rejected": "Error"}
解释:由于其中一个 promise 被拒绝,返回的 promise 也在同一时间被拒绝并返回相同的错误。

示例 3:

输入:functions = [ () => new Promise(resolve => setTimeout(() => resolve(4), 50)), () => new Promise(resolve => setTimeout(() => resolve(10), 150)), () => new Promise(resolve => setTimeout(() => resolve(16), 100)) ]
输出:{"t": 150, "resolved": [4, 10, 16]}
解释:所有的 promise 都成功执行。当最后一个 promise 被解析时,返回的 promise 也被解析了。

提示:

  • 函数 functions 是一个返回 promise 的函数数组
  • 1 <= functions.length <= 10

二、解决方案

概述

在这个问题中,你需要创建一个名为 promiseAll 的 JavaScript 函数,它模拟 JavaScript 内置的 Promise.all() 方法的行为,但不能使用它。这个函数接受一个包含异步函数的数组作为输入,每个函数返回一个 Promise,然后应该返回一个新的 Promise。

返回的 Promise 仅在输入函数返回的所有 Promise 都成功时才会成功。在这种情况下,Promise 的成功值应该是一个数组,包含所有 Promise 的成功值,顺序与输入数组中相应的函数的顺序相同。然而,如果由输入函数返回的任何 Promise 被拒绝,返回的 Promise 应该立即被拒绝,并携带第一个被拒绝的 Promise 的原因。

有效地解决这个问题需要对 JavaScript 的 Promise 和异步编程有很好的理解。你应该熟悉 Promise 的工作方式,如何创建新的 Promise,以及如何处理 Promise 的解决和拒绝。

在 JavaScript 中使用 Promise

在我们的问题中,我们广泛使用 JavaScript Promise,这是异步编程的基本概念。JavaScript 中的 Promise 表示一个值,它可能不会立即可用,但将来会可用,或者由于错误原因永远不可用。Promise 可以处于三种状态之一:待定(Pending)、已成功(Fulfilled)或已拒绝(Rejected)。

在我们的问题背景下,理解这些状态至关重要。我们正在处理一系列返回 Promise 的函数。我们总是创建一个新的 Promise,这个新 Promise 的状态取决于输入数组中 Promise 的状态。如果输入数组中的所有 Promise 都已成功,那么我们的新 Promise 将使用它们的值解决。如果输入数组中的任何 Promise 被拒绝,我们的新 Promise 将立即被拒绝,并携带第一个被拒绝的 Promise 的原因。

Promise.all()

Promise.all() 是 JavaScript 中的一个内置方法,它接受一个 Promise 可迭代对象,并返回一个新的 Promise。这个新 Promise 仅在可迭代对象中的所有 Promise 都已成功时才会被满足,或者在可迭代对象中的任何 Promise 被拒绝时立即被拒绝。Promise.all() 的 Promise 的值是可迭代对象中已满足的 Promise 的值的数组,按照可迭代对象中 Promise 的顺序排列。

let promise1 = Promise.resolve(3);
let promise2 = 42;
let promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
    console.log(values); // [3, 42, "foo"]
});

正如你所看到的,Promise.all() 在你想要并行运行多个 Promise 并等待它们全部完成时非常有用。它是将 Promise 分组在一起并仅在它们全部准备就绪时处理它们的结果的绝佳方式。

然而,目前的问题要求我们在不使用 Promise.all() 的情况下解决它。这要求我们理解 Promise.all() 的内部工作原理,并通过手动处理 Promise、监视它们的状态以及相应地解决或拒绝最终的 Promise 来模拟它的行为。

还值得一提的是,Promise.all() 存在潜在的问题,需要注意:如果传递给它的 Promise 中的任何一个被拒绝,Promise.all() 将立即以该原因拒绝,丢弃所有其他 Promise,即使它们即将被满足。换句话说,它是一个'全体成功或全体失败'的方法。这实际上是我们的问题要求我们模拟的行为。有关更详细的理解,你可以参考 MDN documentation on Promise.all() 的文档。

JavaScript 中 Promise.all() 的使用场景

运行具有相互依赖的任务 可能存在多个异步任务彼此依赖的情况。在这种情况下,Promise.all() 可能非常有用。你可以同时启动所有任务,然后使用结果数组访问每个任务的结果,以正确的顺序。

let task1 = fetch('/api/task1');
let task2 = fetch('/api/task2');
Promise.all([task1, task2]).then(results => {
    let result1 = results[0];
    let result2 = results[1];
    // 处理结果
});

在此示例中,使用 fetch 同时进行两个网络请求。一旦两者都完成,Promise.all() 将解析为一个数组,其中包含两个任务的结果,按照它们添加的顺序排列。这在任务相互依赖但仍然可以并行运行的情况下非常有用。

数据库事务 在数据库操作中,你可能需要执行多个操作,这些操作应该全部成功或全部失败。Promise.all() 允许你将这些操作建模为一个单一的 Promise,该 Promise 在所有操作都成功时或在一个操作失败时立即被拒绝。

let transaction = [UserModel.create({name:'Alice'}), AccountModel.create({userId:'Alice',balance:100})];
Promise.all(transaction).then(()=> console.log('事务成功')).catch(()=> console.log('事务失败'));

在此示例中,我们使用 Promise.all() 执行涉及创建用户和为用户创建帐户的事务。如果其中任何操作失败,Promise.all() 将立即拒绝,允许我们轻松回滚事务。

汇总 API 数据 在实际应用中,你可能需要从多个不同的 API 端点获取数据,然后才能呈现页面或计算一些结果。与等待每个请求完成后才开始下一个请求不同,Promise.all() 允许你同时进行所有请求,然后等待它们全部完成。

let urls = ['https://api.github.com/users/github','https://api.github.com/users/microsoft','https://api.github.com/users/apple'];
Promise.all(urls.map(url=>fetch(url).then(user=> user.json()))).then(users=>{
    console.log(users.length);// 3
    console.log(users[0]);// {login: "github", ...}
});

在此示例中,我们使用 Promise.all() 从多个 GitHub 帐户获取用户数据。这加快了数据获取过程,因为所有请求都同时进行。

方法 1:模拟 Promise.all() 的行为
概述

目标是复制 JavaScript 内置的 Promise.all() 方法的功能。具体来说,我们需要管理一组返回 Promise 的函数,并返回一个 Promise,该 Promise 解析为结果数组,保留原始数组的顺序。我们将自己处理 Promise 的解析,可以使用现代的 async/await 语法或经典的 then/catch 语法。

算法
  1. 从 promiseAll 函数返回一个新 Promise。
  2. 如果输入数组为空,立即用一个空数组解析它并返回。
  3. 初始化一个数组 res 以保存结果,最初填充为 null。
  4. 初始化一个 resolvedCount 变量,用于跟踪已解析的 Promise 数。
  5. 迭代 Promise 返回函数的数组。对于每个返回 Promise 的函数:
    • 在 async/await 版本中,等待 Promise。在解析时,将结果放入 res 数组中的相应位置并增加 resolvedCount。如果引发错误,立即用错误拒绝 Promise。
    • 在 then/catch 版本中,附加一个 then 子句和一个 catch 子句。在解析时,then 子句将结果放入 res 数组中并增加 resolvedCount。catch 子句用错误拒绝 Promise。

如果所有 Promise 都已解析(即 resolvedCount 等于函数数组的长度),则使用 res 数组解析 promiseAll() Promise。

async/await 版本和 then/catch 版本的主要区别在于语法和等待/处理 Promise 的方式,但总体方法保持不变。这两种实现都确保所有 Promise 同时开始(而不是按顺序),并且返回的 Promise 解析为它们的结果数组,保持原始顺序。

实现
实现 1:使用 async/await 语法
var promiseAll = async function(functions) {
    return new Promise((resolve, reject) => {
        if (functions.length === 0) {
            resolve([]);
            return;
        }
        const res = new Array(functions.length).fill(null);
        let resolvedCount = 0;
        functions.forEach(async (el, idx) => {
            try {
                const subResult = await el();
                res[idx] = subResult;
                resolvedCount++;
                if (resolvedCount === functions.length) {
                    resolve(res);
                }
            } catch (err) {
                reject(err);
            }
        });
    });
};

这段代码使用 async/await 语法,它比传统的 Promise 语法更现代,通常更易于阅读。它初始化与输入数组长度相同的空值数组。然后,它使用 forEach 迭代输入数组,运行每个函数,并在解析后将结果数组中相应的空值替换为函数的返回值。如果所有函数都成功解析,则 promiseAll() 返回的 Promise 将与结果数组一起解析。如果任何函数拒绝,promiseAll() 返回的承诺将立即以第一个拒绝的函数提供的原因拒绝。

实现 2:使用 then/catch 语法
var promiseAll = function(functions) {
    return new Promise((resolve, reject) => {
        if (functions.length === 0) {
            resolve([]);
            return;
        }
        const res = new Array(functions.length).fill(null);
        let resolvedCount = 0;
        functions.forEach((el, idx) => {
            el().then((subResult) => {
                res[idx] = subResult;
                resolvedCount++;
                if (resolvedCount === functions.length) {
                    resolve(res);
                }
            }).catch((err) => {
                reject(err);
            });
        });
    });
};

这段代码与第一个实现非常相似,但使用了传统的 Promise 语法,而不是 async/await。输入数组中的每个函数都会运行,并且会调用 then 方法来处理它们的解析或 catch 方法来处理它们的拒绝。如果所有函数都成功解决,promiseAll() 返回的 Promise 将解析为结果数组。如果任何函数拒绝,promiseAll() 返回的 Promise 将立即拒绝,并携带第一个拒绝的函数提供的原因。

复杂度分析

时间复杂度:O(N),其中 N 是传递给 promiseAll() 的函数数目。这是因为 promiseAll() 本质上是等待所有 N 个 Promise 解析或拒绝,因此时间复杂度与 Promise 数目成正比。请注意,这不包括运行为 Promise 运行的单个函数的时间复杂度,它侧重于 promiseAll() 本身的操作。 空间复杂度:O(N),其中 N 是传递给 promiseAll() 的函数数目。主要用于存储 Promise 结果。与时间复杂度一样,空间复杂度与 Promise 数目成正比。

面试提示:
  1. Promise.all() 是什么,它是如何工作的?Promise.all() 是 JavaScript 中的一个实用函数,它将多个 Promise 聚合成一个单一的 Promise,该 Promise 仅在所有输入 Promise 都已解决时才会解决,或者在输入 Promise 中的任何一个拒绝时立即拒绝。它通常用于需要同时执行多个异步操作,并且进一步的计算取决于这些操作的完成。
  2. 如果传递给 Promise.all() 的 Promise 中有一个拒绝会发生什么?如果传递给 Promise.all() 的 Promise 中有一个拒绝,Promise.all() 返回的 Promise 将立即被拒绝,并携带第一个拒绝的 Promise 的原因。这种行为有时被称为'快速失败'。
  3. 如何处理 Promise.all() 中的单个 Promise 拒绝?要处理 Promise.all() 中的单个 Promise 拒绝,你可以捕获单个 Promise 中的错误并将其转换为带有错误值的解决。这样,Promise.all() 将始终解决,而错误处理可以在生成的值数组上执行。但是,从 ECMAScript 2020 开始,更好的选择是使用 Promise.allSettled()。
  4. Promise.all() 和 Promise.allSettled() 之间有什么区别?Promise.allSettled() 方法与 Promise.all() 类似,但有一个关键区别。虽然 Promise.all() 只要其中一个 Promise 拒绝就会拒绝,Promise.allSettled() 在所有 Promise 已解决时或已拒绝时都会解决。Promise.allSettled() 的解析值是一个对象数组,每个对象都描述每个 Promise 的结果。

目录

  1. 2721. 并行执行异步函数
  2. 一、题目
  3. 二、解决方案
  4. 概述
  5. 在 JavaScript 中使用 Promise
  6. Promise.all()
  7. JavaScript 中 Promise.all() 的使用场景
  8. 方法 1:模拟 Promise.all() 的行为
  9. 概述
  10. 算法
  11. 实现
  12. 实现 1:使用 async/await 语法
  13. 实现 2:使用 then/catch 语法
  14. 复杂度分析
  15. 面试提示:
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Docker 容器核心操作与运维实战指南
  • Stable Diffusion WebUI 本地部署指南(CUDA/cuDNN/PyTorch 配置)
  • ToClaw:基于 OpenClaw 的云端 AI 自动化助手评测
  • xAI 计划推出独立应用,Grok 将直面 ChatGPT 竞争
  • Talker-Reasoner:基于双系统架构的 AI Agent 设计
  • JDBC 连接 MySQL 数据库时 SSL 连接警告的处理方法
  • Python 基于 DXGI 实现现代游戏窗口无闪烁高性能截图方案
  • React Native 热更新方案:现状与选型分析
  • Linux 高频面试题与详细解析
  • 基于 SpringBoot 的高校图书馆借阅管理系统设计与实现
  • MySQL Workbench 导入 SQL 文件完整指南
  • 自然语言处理在金融领域的应用与实战
  • MySQL 数据类型深度解析:选对类型提升性能
  • OpenClaw 本地部署进阶:接入 Telegram 与网页搜索能力
  • C++ STL list 模拟实现:从底层链表到容器封装
  • React 前端项目部署至 Nginx 服务器并实现外网访问
  • Android 项目开发整体架构设计与演进
  • llama.cpp 核心特性、原理与实战部署指南
  • Java 消息队列选型总结:RabbitMQ、RocketMQ、Kafka 实战对比
  • MySQL 数据类型详解:选型要点与避坑指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online