Array.fromAsync()

Baseline 2024
新推出

自 ⁨2024 年 1 月⁩起,此特性已在最新的设备和浏览器版本中可用。此特性可能无法在较旧的设备或浏览器上使用。

Array.fromAsync() 静态方法从一个 异步可迭代对象可迭代对象类数组对象 创建一个新的、浅拷贝的 Array 实例。

语法

js
Array.fromAsync(items)
Array.fromAsync(items, mapFn)
Array.fromAsync(items, mapFn, thisArg)

参数

items

一个要转换为数组的异步可迭代对象、可迭代对象或类数组对象。

mapFn 可选

一个用于调用数组中每个元素的函数。如果提供了此参数,则添加到数组中的每个值都将首先通过此函数,并且 mapFn 的返回值将添加到数组中(在 await 之后)。该函数将使用以下参数调用:

element

正在数组中处理的当前元素。如果 items 是同步可迭代对象或类数组对象,则所有元素都会先被 awaitelement 永远不会是 thenable。如果 items 是异步可迭代对象,则每个生成的值都按原样传递。

index

数组中正在处理的当前元素的索引。

thisArg 可选

执行 mapFn 时用作 this 的值。

返回值

一个新的 Promise,其实现值为一个新的 Array 实例。

描述

Array.fromAsync() 允许你从以下对象创建数组:

Array.fromAsync() 以非常类似于 for await...of 的方式迭代异步可迭代对象。如果 items 是异步可迭代对象或同步可迭代对象,则 Array.fromAsync(items) 通常等同于以下代码:

js
const result = [];
for await (const element of items) {
  result.push(element);
}

在行为上,Array.fromAsync() 几乎等同于 Array.from(),但有以下区别:

  • Array.fromAsync() 处理异步可迭代对象。
  • Array.fromAsync() 返回一个 Promise,该 Promise 实现为数组实例。
  • 如果使用非异步可迭代对象调用 Array.fromAsync(),则添加到数组的每个元素都会先被 await
  • 如果提供了 mapFn,其输出也会在内部被 await

Array.fromAsync()Promise.all() 都可以将 Promise 可迭代对象转换为 Promise 数组。但是,它们之间存在两个关键区别:

  • Array.fromAsync() 顺序等待可迭代对象生成的每个值。Promise.all() 并发等待所有值。
  • Array.fromAsync() 惰性迭代可迭代对象,直到当前值稳定后才检索下一个值。Promise.all() 提前检索所有值并等待它们全部完成。

示例

从异步可迭代对象创建数组

js
const asyncIterable = (async function* () {
  for (let i = 0; i < 5; i++) {
    await new Promise((resolve) => setTimeout(resolve, 10 * i));
    yield i;
  }
})();

Array.fromAsync(asyncIterable).then((array) => console.log(array));
// [0, 1, 2, 3, 4]

items 是一个异步可迭代对象,其中每个结果的 value 本身也是一个 Promise 时,这些 Promise 会被添加到结果数组中,而不会被 await。这与 for await...of 的行为一致。

js
function createAsyncIter() {
  let i = 0;
  return {
    [Symbol.asyncIterator]() {
      return {
        async next() {
          if (i > 2) return { done: true };
          i++;
          return { value: Promise.resolve(i), done: false };
        },
      };
    },
  };
}

Array.fromAsync(createAsyncIter()).then((array) => console.log(array));
// (3) [Promise, Promise, Promise]

注意: 实际上,你很少会遇到生成 Promise 的异步可迭代对象,因为如果你使用 异步生成器函数 来实现,那么 yield 表达式会自动解开 Promise。

从同步可迭代对象创建数组

js
Array.fromAsync(
  new Map([
    [1, 2],
    [3, 4],
  ]),
).then((array) => console.log(array));
// [[1, 2], [3, 4]]

从生成 Promise 的同步可迭代对象创建数组

js
Array.fromAsync(
  new Set([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]),
).then((array) => console.log(array));
// [1, 2, 3]

从 Promise 的类数组对象创建数组

js
Array.fromAsync({
  length: 3,
  0: Promise.resolve(1),
  1: Promise.resolve(2),
  2: Promise.resolve(3),
}).then((array) => console.log(array));
// [1, 2, 3]

在同步可迭代对象中使用 mapFn

items 是同步可迭代对象或类数组对象时,Array.fromAsync() 会在内部 await mapFn 的输入和输出。

js
function delayedValue(v) {
  return new Promise((resolve) => setTimeout(() => resolve(v), 100));
}

Array.fromAsync(
  [delayedValue(1), delayedValue(2), delayedValue(3)],
  (element) => delayedValue(element * 2),
).then((array) => console.log(array));
// [2, 4, 6]

在异步可迭代对象中使用 mapFn

items 是异步可迭代对象时,mapFn 的输入不会被 await,但输出会被 await。使用与上面相同的 createAsyncIter 函数:

js
Array.fromAsync(createAsyncIter(), async (element) => (await element) * 2).then(
  (array) => console.log(array),
);
// [2, 4, 6]

有趣的是,这意味着 Array.fromAsync(createAsyncIter()) 不等同于 Array.fromAsync(createAsyncIter(), (element) => element),因为前者会 await 每个生成的值,而后者则不会。

js
Array.fromAsync(createAsyncIter(), (element) => element).then((array) =>
  console.log(array),
);
// [1, 2, 3]

与 Promise.all() 的比较

Array.fromAsync() 顺序等待可迭代对象生成的每个值。Promise.all() 并发等待所有值。

js
function* makeIterableOfPromises() {
  for (let i = 0; i < 5; i++) {
    yield new Promise((resolve) => setTimeout(resolve, 100));
  }
}

(async () => {
  console.time("Array.fromAsync() time");
  await Array.fromAsync(makeIterableOfPromises());
  console.timeEnd("Array.fromAsync() time");
  // Array.fromAsync() time: 503.610ms

  console.time("Promise.all() time");
  await Promise.all(makeIterableOfPromises());
  console.timeEnd("Promise.all() time");
  // Promise.all() time: 101.728ms
})();

同步可迭代对象没有错误处理

for await...of 类似,如果正在迭代的对象是同步可迭代对象,并且在迭代过程中抛出错误,则不会调用底层迭代器的 return() 方法,因此迭代器不会被关闭。

js
function* generatorWithRejectedPromises() {
  try {
    yield 0;
    yield Promise.reject(new Error("error"));
  } finally {
    console.log("called finally");
  }
}

(async () => {
  try {
    await Array.fromAsync(generatorWithRejectedPromises());
  } catch (e) {
    console.log("caught", e);
  }
})();
// caught Error: error
// No "called finally" message

如果你需要关闭迭代器,你需要改用 for...of 循环,并自己 await 每个值。

js
(async () => {
  const arr = [];
  try {
    for (const val of generatorWithRejectedPromises()) {
      arr.push(await val);
    }
  } catch (e) {
    console.log("caught", e);
  }
})();
// called finally
// caught 3

规范

规范
ES Array.fromAsync
# sec-array.fromAsync

浏览器兼容性

另见