当前位置:首页 > 文章列表 > 文章 > 前端 > JavaScript Array.fromAsync 怎么收集异步可迭代对象

JavaScript Array.fromAsync 怎么收集异步可迭代对象

来源:17golang原创 2026-10-04 22:58:00 0浏览 收藏

收集异步可迭代对象,最小写法就是 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.fromAsync 三类输入、异步映射与数组 Promise 返回结果的静态关系图
图1:Array.fromAsync 接受异步可迭代、同步可迭代和类数组输入,可在收集过程中执行并等待 mapFn,最终返回保持输入顺序的数组 Promise;这是静态关系图。

对于同步可迭代对象,迭代得到的每个值也会被等待。这与 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 并发集合等待的静态对比图
图2:Array.fromAsync 适合逐项拉取并顺序等待的异步数据源;Promise.all 适合已经准备好的独立 Promise 集合并发等待;这是静态选择边界图。
判断项Array.fromAsyncPromise.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(),边界就清楚了。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Linux user namespace 的 uid_map 怎么配置Linux user namespace 的 uid_map 怎么配置
上一篇
Linux user namespace 的 uid_map 怎么配置
Go image/draw.DrawMask 怎么按透明蒙版合成图片
下一篇
Go image/draw.DrawMask 怎么按透明蒙版合成图片
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    329次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    386次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    380次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    350次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    174次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码