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

瀏覽器相容性

另見