JavaScript Array.fromAsync 怎么收集异步可迭代对象
收集异步可迭代对象,最小写法就是 const list = await Array.fromAsync(source)。它会逐项读取数据源,等待每个值,并在迭代完成后返回一个兑现为普通数组的 Promise。分页接口、异步生成器和按需读取的数据流,都可以用这条语句汇总成数组。
Array.fromAsync()接受异步可迭代、同步可迭代和类数组对象。- 读取与映射按输入顺序进行;
mapFn的异步结果也会被等待。 - 它会把全部结果保存在内存中,不适合无限流或超大数据集。
MDN 参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync
ECMAScript 规范:https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.fromasync
最小可用写法
下面的异步生成器先产出一个值,短暂等待后再产出第二个值。Array.fromAsync() 会一直等到生成器结束,因此调用端必须使用 await,或者继续处理它返回的 Promise。
async function* numbers() {
// 第一个值可以立即被消费。
yield 1;
await new Promise((resolve) => setTimeout(resolve, 20));
// 第二个值在异步工作完成后才出现。
yield 2;
}
const result = await Array.fromAsync(numbers());
console.log(result); // [1, 2]
返回值不是“边读边可用”的数组,而是最终完成时一次性得到的数组。若调用方需要收到一个处理一个,应直接使用 for await...of,不要先收集。
Array.fromAsync 能收集哪些输入
该方法覆盖三类输入。第一类是实现 Symbol.asyncIterator 的异步可迭代对象,例如异步生成器;第二类是实现 Symbol.iterator 的同步可迭代对象,例如数组、Set;第三类是带有 length 和数字下标的类数组对象。无论输入属于哪一类,结果都是一个浅拷贝数组。

对于同步可迭代对象,迭代得到的每个值也会被等待。这与 Array.from() 不同:后者立即返回数组,不会等待 Promise 元素。
const tasks = [Promise.resolve("A"), Promise.resolve("B")];
// Array.from() 只复制 Promise,不等待它们。
const pending = Array.from(tasks);
// Array.fromAsync() 等待每个值后再写入结果数组。
const values = await Array.fromAsync(tasks);
console.log(values); // ["A", "B"]
收集分页异步生成器
更常见的场景是把分页接口包装成异步生成器。生成器负责游标、请求和逐项产出,调用端只关心最终数组。下面的 loadPages() 每次仅请求一页,不会预先创建所有请求。
async function* loadPages(fetchPage) {
let cursor = null;
do {
// 当前页完成后才会继续读取下一页。
const page = await fetchPage(cursor);
for (const item of page.items) {
// 逐项产出,避免在生成器内部再保存一份总数组。
yield item;
}
cursor = page.nextCursor;
} while (cursor !== null);
}
const users = await Array.fromAsync(
loadPages(fetchPage),
async (user, index) => {
// mapFn 返回 Promise 时会被等待,结果仍保持输入顺序。
const profile = await loadProfile(user.id);
return { ...user, profile, order: index };
},
);
mapFn 的第二个参数是从 0 开始的索引。若映射函数需要固定上下文,可以传第三个参数 thisArg;箭头函数不会绑定自己的 this,此时应改用普通函数。
const formatter = {
prefix: "用户",
format(user) {
// 普通函数通过 thisArg 读取 formatter。
return `${this.prefix}-${user.id}`;
},
};
const labels = await Array.fromAsync(
loadPages(fetchPage),
formatter.format,
formatter,
);
与 Promise.all 的选择边界
Array.fromAsync() 和 Promise.all() 都能得到数组,但调度方式不同。前者从数据源逐项取值并等待,再请求下一项;后者接收一个已经准备好的 Promise 集合,通常让它们并发推进。它们解决的不是同一个问题。

| 判断项 | Array.fromAsync | Promise.all |
|---|---|---|
| 输入 | 异步可迭代、可迭代或类数组 | 可迭代的 Promise/值集合 |
| 取值方式 | 逐项、懒式读取 | 集合通常已经建立 |
| 等待方式 | 顺序等待每个值与映射结果 | 多个 Promise 并发等待 |
| 适合 | 分页、限速源、前一项决定后一项 | 互相独立且可并发的请求 |
如果你已经有一组互相独立的 URL,并希望尽快完成所有请求,可以先 map() 创建 Promise,再交给 Promise.all()。如果数据源只能按需给出下一项,或希望自然限制请求节奏,优先使用 Array.fromAsync()。
mapFn 与 Promise 值的细节
对同步可迭代输入,元素中的 Promise 会被等待;对异步可迭代输入,迭代协议通常直接提供已完成值。异步生成器会自动展开 yield 的 Promise,但自定义异步迭代器可以更灵活。若来源实现不确定,而业务要求结果中绝不能保留 Promise,可显式使用映射函数:
const settledValues = await Array.fromAsync(
customAsyncIterable,
async (value) => {
// 再显式等待一次,确保映射结果是业务值。
return await value;
},
);
映射在写入最终数组之前完成,因此比“先收集,再调用异步 map”更直接。不要写 await array.map(async ...),因为 map() 返回的是 Promise 数组;若输入本来就是普通数组且需要并发映射,应使用 await Promise.all(array.map(async ...))。
失败、清理与取消
读取下一项、等待元素或执行 mapFn 任一阶段抛错,Array.fromAsync() 返回的 Promise 都会拒绝。调用方应在业务边界使用 try...catch。同步迭代器在异常时会触发关闭流程;自定义异步数据源仍应通过 try...finally 释放连接、文件句柄或订阅。
async function* readEvents(signal) {
const connection = await openConnection();
try {
while (!signal.aborted) {
// 数据源自行观察 AbortSignal,才能真正停止底层工作。
yield await connection.next();
}
} finally {
// 正常结束、异常或提前 return 都执行资源清理。
await connection.close();
}
}
const controller = new AbortController();
try {
const events = await Array.fromAsync(readEvents(controller.signal));
console.log(events);
} catch (error) {
// 在这里记录上下文或转换成业务错误。
console.error("收集事件失败", error);
}
Array.fromAsync() 本身没有 signal 参数,取消能力必须由数据源实现。只调用 controller.abort() 而数据源从不检查信号,并不会自动终止迭代。
无限流与内存边界
该方法只有在迭代结束后才兑现结果,而且会把全部元素留在数组中。因此不要直接收集日志流、消息订阅或没有结束条件的传感器流。可以先加数量边界,再收集有限窗口:
async function* take(source, limit) {
let count = 0;
for await (const item of source) {
// 达到上限后 return,触发上游迭代器关闭。
if (count >= limit) return;
count += 1;
yield item;
}
}
// 只把前 100 条放入内存,避免无界增长。
const sample = await Array.fromAsync(take(eventStream, 100));
如果每条数据都能独立落库、写文件或更新统计值,直接用 for await...of 逐项处理更稳妥;只有确实需要随机访问、排序、整体校验或一次性提交时,才值得收集成数组。
兼容性与轻量回退
MDN 将 Array.fromAsync() 标记为 Baseline,主流浏览器自 2024 年起已经广泛可用,但旧浏览器和旧运行时仍可能缺少该静态方法。上线前可做特性检测,并用 for await...of 实现常见语义的轻量回退:
async function collectAsync(items, mapFn = (value) => value) {
const result = [];
let index = 0;
for await (const item of items) {
// 顺序等待映射结果,接近常见的 Array.fromAsync 用法。
result.push(await mapFn(item, index));
index += 1;
}
return result;
}
const collect = typeof Array.fromAsync === "function"
? Array.fromAsync.bind(Array)
: collectAsync;
const list = await collect(loadPages(fetchPage));
这段回退覆盖异步可迭代和同步可迭代对象,但没有完整复刻规范中的类数组、构造器与所有异常边界。若项目需要完整兼容,应使用维护中的 polyfill,并在目标运行时执行测试。
快速排查清单
- 得到的是 Promise 而不是数组:调用处漏了
await。 - 收集一直不结束:数据源是无限流,或退出条件从未满足。
- 速度比 Promise.all 慢:顺序等待是设计行为;独立任务应改用并发集合。
- 内存持续上涨:正在把过多元素汇总到一个数组,应分批或流式处理。
- 取消无效:异步迭代器没有观察 AbortSignal,也没有在 finally 中释放资源。
- 旧环境报“不是函数”:先做特性检测,再加载 polyfill 或使用回退函数。
小结:用 await Array.fromAsync(asyncIterable) 就能把异步可迭代对象收集为数组;需要转换时把异步 mapFn 作为第二个参数传入。它的优势是按需取值、顺序明确,代价是最终仍要把全部结果放进内存。数据无界时流式处理,任务彼此独立且要并发时选择 Promise.all(),边界就清楚了。
Linux user namespace 的 uid_map 怎么配置
- 上一篇
- Linux user namespace 的 uid_map 怎么配置
- 下一篇
- Go image/draw.DrawMask 怎么按透明蒙版合成图片
-
- 文章 · 前端 | 3小时前 | html · 前端 · javascript · slot Web Components 服务端渲染 Declarative Shadow DOM shadowrootmode Shadow Root
- Declarative Shadow DOM 怎么服务端输出组件结构
- 227浏览 收藏
-
- 文章 · 前端 | 6小时前 | 前端 · 性能监控 · javascript · 前端性能 PerformanceObserver INP Long Animation Frames API LoAF 卡顿脚本
- Long Animation Frames API 怎么定位卡顿脚本
- 463浏览 收藏
-
- 文章 · 前端 | 8小时前 | html · javascript · css · html Popover API popovertarget 前端弹层
- HTML Popover API 怎么实现轻量弹层
- 444浏览 收藏
-
- 文章 · 前端 | 10小时前 | websocket · javascript · 异步编程 · JavaScript websocket AbortSignal 异步迭代器 Promise.withResolvers EventTarget
- JavaScript Promise.withResolvers 适合哪些事件桥接场景
- 431浏览 收藏
-
- 文章 · 前端 | 13小时前 | javascript · JavaScript Fetch AbortController 取消请求 AbortSignal.any AbortSignal.timeout
- JavaScript AbortSignal.any 怎么组合多个取消来源
- 174浏览 收藏
-
- 文章 · 前端 | 15小时前 |
- JavaScript scheduler.yield 怎么让长任务主动让出主线程
- 255浏览 收藏
-
- 文章 · 前端 | 17小时前 |
- View Transition API 怎么给跨文档导航添加过渡
- 181浏览 收藏
-
- 文章 · 前端 | 19小时前 | css · CSS 自定义属性 container query
- CSS Style Container Query 怎么按自定义属性切换样式
- 440浏览 收藏
-
- 文章 · 前端 | 22小时前 | typescript · 前端工程 · VUE shallowRef 第三方实例 triggerRef markRaw
- Vue shallowRef 管理第三方实例的响应式边界
- 246浏览 收藏
-
- 文章 · 前端 | 23小时前 | React useTransition startTransition 并发渲染
- React useTransition 区分交互更新与后台渲染
- 112浏览 收藏
-
- 文章 · 前端 | 1天前 | vite 前端缓存 Vite依赖预构建 optimizeDeps node_modules/.vite
- Vite 依赖预构建缓存失效时的排查步骤
- 451浏览 收藏
-
- 文章 · 前端 | 2天前 |
- 表单 aria-describedby 关联错误提示的可访问设计
- 349浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 329次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 386次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 380次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 350次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 174次使用
-
- Go语言对前端领域的入侵WebAssembly运行原理
- 2022-12-31 130浏览
-
- 这样需求的表单一般要这么做?
- 2023-02-17 194浏览
-
- go+gin 静态资源路由与后端api路由冲突如何解决?
- 2023-02-24 414浏览
-
- css 中的px 跟 video 中px 等同吗?
- 2023-01-09 427浏览
-
- 为什么使用了https还是可以被抓包
- 2023-02-16 480浏览

