当前位置: 首页 > news >正文

手写 Promise.all 的原理与实现

一、引言

Promise.all 是 JavaScript 异步编程中处理并发任务的重要 API。它接收一个可迭代对象,返回一个新的 Promise。当所有输入 Promise 都成功时,新 Promise 以结果数组的形式成功;当任一输入 Promise 失败时,新 Promise 立即失败并返回该错误。 本文将从规范出发,剖析其底层运行机制,并给出两种实现方案及对比分析。


二、规范与特性

根据 ECMMAScript 规范,Promise.all 具有以下特性:

  1. 输入类型
    必须是可迭代对象(Array、Set 等实现了 Symbol.iterator 接口的对象)。若输入非可迭代对象,应抛出 TypeError

  2. 空输入处理
    若输入为空可迭代对象,应立即返回已成功状态的 Promise,结果为空数组。

  3. 成功条件
    所有 Promise 成功后,返回一个按输入顺序排列的结果数组。

  4. 失败条件
    只要有一个 Promise 失败,Promise.all 立即以该原因失败,忽略其他未完成 Promise 的结果。


三、实现思路

1. 输入校验

通过检测 Symbol.iterator 方法判断是否为可迭代对象:

if (typeof iterable[Symbol.iterator] !== 'function') {return Promise.reject(new TypeError('The input is not iterable'));
}

2. 空输入处理

将输入转换为数组,若长度为 0,直接返回成功状态:

const promises = Array.from(iterable);
if (promises.length === 0) {return Promise.resolve([]);
}

3. 异步任务管理

使用一个数组存储结果,一个计数器统计已完成的 Promise 数量:

  • 每个 Promise 成功时,将结果存入对应索引位置,计数器加一;
  • 当计数器等于总任务数时,调用 resolve 返回结果数组;
  • 任何一个 Promise 失败时,直接调用 reject 抛出错误。

核心逻辑:

return new Promise((resolve, reject) => {const results = new Array(promises.length);let resolvedCount = 0;for (let i = 0; i < promises.length; i++) {Promise.resolve(promises[i]).then(value => {results[i] = value;resolvedCount++;if (resolvedCount === promises.length) {resolve(results);}}).catch(reject);}
});

四、两种实现方案对比

方案一:外部保存 resolve/reject

Promise.myAll = function (iterable) {if (typeof iterable[Symbol.iterator] !== 'function') {return Promise.reject(new TypeError('The input is not iterable'));}let resolver, rejecter;const p = new Promise((resolve, reject) => {resolver = resolve;rejecter = reject;});const promises = Array.from(iterable);const n = promises.length;if (n === 0) {resolver([]);return p;}const results = new Array(n);let resolvedCount = 0;for (let i = 0; i < n; i++) {Promise.resolve(promises[i]).then(value => {results[i] = value;resolvedCount++;if (resolvedCount === n) {resolver(results);}}).catch(err => rejecter(err));}return p;
};

特点

  • resolve/reject 暴露到外部变量;
  • 逻辑拆分清晰,但存在变量暴露风险。

方案二:内部封装 resolve/reject

Promise.myAll = function (iterable) {if (typeof iterable[Symbol.iterator] !== 'function') {return Promise.reject(new TypeError('The input is not iterable'));}return new Promise((resolve, reject) => {const promises = Array.from(iterable);const n = promises.length;if (n === 0) {return resolve([]);}const results = new Array(n);let resolvedCount = 0;for (let i = 0; i < n; i++) {Promise.resolve(promises[i]).then(value => {results[i] = value;resolvedCount++;if (resolvedCount === n) {resolve(results);}}).catch(reject);}});
};

特点

  • 状态控制函数封装在 Promise 内部,安全性更高;
  • 代码简洁,符合最小权限原则。

五、测试验证

为确保实现符合规范,需覆盖以下测试场景:

  1. 空输入

    Promise.myAll([]).then(res => console.log('空输入:', res)); // []
    
  2. 全部成功(含非 Promise 值)

    Promise.myAll([Promise.resolve(1), 2, 3]).then(res => console.log('全部成功:', res)); // [1, 2, 3]
    
  3. 任一失败

    Promise.myAll([Promise.resolve(1), Promise.reject('error'), Promise.resolve(3)]).catch(err => console.error('失败原因:', err)); // error
    
  4. 非可迭代输入

    Promise.myAll(123).catch(err => console.error('类型错误:', err.message)); // The input is not iterable
    

六、常见问题与注意事项

  1. 非 Promise 输入处理
    必须使用 Promise.resolve 将输入值统一转换为 Promise,否则非 Promise 值会导致 then 调用报错。

  2. 结果顺序保证
    使用固定长度数组按索引存储结果,确保输出顺序与输入顺序一致。

  3. 快速失败机制
    利用 Promise 状态不可逆特性,一旦 reject 后,后续的 resolve 或 reject 都不会改变最终状态。


七、结论

Promise.all 的本质是一个并发任务管理器,通过统一输入处理、顺序结果收集和快速失败机制,实现了对批量异步任务的高效管理。手写实现不仅有助于理解其底层原理,也能在实际开发中更好地运用该 API。在实现方式上,推荐使用内部封装 resolve/reject 的写法,以提高代码安全性与简洁性。

http://www.dtcms.com/a/457025.html

相关文章:

  • 关于windows系统事件查看器的初步理解
  • Linux 线程概念与虚拟地址空间深度解析
  • 一套智慧工地云平台源码,支持监管端、项目管理端,Java+Spring Cloud +UniApp +MySql技术开发
  • 虚幻引擎5 GAS开发俯视角RPG游戏 P05-05 游戏效果委托
  • 音频audio播放两种方式:MediaPlayer和AudioTrack对比
  • K8s学习笔记(十五) pause容器与init容器
  • DVWA靶场之十六:未验证的重定向漏洞(Open HTTP Redirect)
  • 上海网站建设免费推做网站的软件 简单易学
  • 面部情绪识别数据集的介绍和下载
  • Golang中的HTTP请求凝聚器
  • 网站建设多少钱一平米中铁建设集团门户网登陆
  • Linux shell学习(更新中....)
  • 自动生成API文档与故障排查决策树的NLP应用
  • 手机怎么制作钓鱼网站建设文明网 联盟网站的
  • Rust 的类型自动解引用:隐藏在人体工学设计中的魔法
  • AVX-512深度实现分析:从原理到LLaMA.cpp的性能优化艺术
  • 前端玩转大模型,DeepSeek-R1 蒸馏 Llama 模型的 Bedrock 部署
  • 计算机网络-运输层
  • OSPF协议详解5:实验 - 计时器、度量值与其他高级配置
  • OpenCV(五):鼠标控制
  • Linux中权限系统
  • 网站域名到期后果四川人力资源考试官网二建
  • python爬虫(五) ---- Pyinstaller打包Python程序为exe文件及遇到的问题
  • 沈阳做网站价格自己做网站要学什么
  • 深入浅出ArkTS:HarmonyOS应用开发的现代化语法解析
  • UVa 204 Robot Crash
  • 2025 完整指南:Gemini 2.5 Computer Use 模型 - AI Agent 界面控制的革命性突破
  • 云南网站建设专业品牌网站域名怎么转
  • Vue项目中如何实现表格选中数据的 Excel 导出
  • 【多模态学习】QA7: GRPO算法?KL散度指的是什么?什么叫做长思维连冷启动?模型退火是什么意思?