JavaScriptのmapでasync/awaitするには?

javascript map async イメージ画像

JavaScriptでは、.mapを使って配列のループ処理を手軽に作ることができます。

ですが、.mapの中で非同期処理を呼び出したいとき、async関数の前にawaitを付けるだけでは非同期処理の結果をうまく受け取ることができません。

JavaScriptの.map処理の中で、非同期処理の結果を受け取るには、await Promise.all()でmap処理を囲みます。

具体的なコードと一緒に、詳細を確認してみましょう。

目次

JavaScriptのmapの中でasync/awaitするにはawait Promise.allを使う

誤った処理の例

よくある誤った処理として、下記のような状態となってしまっていることが多いと思います。

コード

const asyncFunc = async (user) => {
  return `Hello ${user}`;
};

const fetchData = async () => {
  const userList = ["John", "Jane", "Jack"];
  const helloMessages = userList.map(async (user) => {
    return await asyncFunc(user);
  });
  console.log(helloMessages);
};

(() => fetchData())();

サンプルコードの内容

  1. fetchData関数内の配列:userListでループを行います。
  2. asyncFunc関数に配列の各要素を渡して、”Hello ユーザー名”という値を受け取ります。
  3. 受け取った値はhelloMessagesに追加していきます。
  4. 最後に、console.log(helloMessages)で中身の確認をします。

※本来であれば、この処理内容なら非同期処理を使わずに処理可能です。

 map内でasync/awaitを使う処理の説明用に非同期処理としていることをご理解ください。

この処理を書いたAさんは、以下のようなことを考えました。

  • map処理の中で非同期処理のasyncFuncを呼び出したいので、asyncFuncの前にawaitを書きました。
  • awaitはasync関数の中でしか使えないので、map(async (user) => {}の形式にしました。
    • asyncを記載する位置は、コードエディターが自動で補完してくれたので、意味はよくわかっていません。

とりあえず、コードエディターに怒られる箇所はなくなりました。試しに実行してみましょう。

処理結果

(3) [Promise, Promise, Promise]

配列にはなっていますが、中身がPromiseになってしまっていますね。

正しい処理

先程の内容を、正しく直してみましょう。

コード

const asyncFunc = async (user) => {
  return `Hello ${user}`;
};

const fetchData = async () => {
  const userList = ["John", "Jane", "Jack"];
  const helloMessages = await Promise.all(
    userList.map(async (user) => {
      return await asyncFunc(user);
    })
  );
  console.log(helloMessages);
};

(() => fetchData())();

ポイントは、userList.map(…)の外側の、await Promise.all()です。

処理結果

(3) ['Hello John', 'Hello Jane', 'Hello Jack']

今度は想定通りの結果が返ってきました。

Promiseとは

では、誤った処理で返ってきたPromiseとは何なのでしょうか?

Promiseとは、なんらかの値を返すことが約束されているオブジェクトで、値がまだ確定していない状態です。

非同期処理は処理結果を直接返すのではなく、Promiseを返します。

JavaScript Promise概要

awaitを使わない場合、非同期処理からPromiseがそのまま返ってきます。

JavaScript Promise - awaitを使った場合

awaitを使うと、Promiseの値の確定を待ちます。

しかし、今回はmapの中でawaitを使っていますよね。

なぜPromiseが返ってきてしまったのでしょう。

もう一度ソースコードを見て、理由を考えてみましょう。

const asyncFunc = async (user) => {
  return `Hello ${user}`;
};

const fetchData = async () => {
  const userList = ["John", "Jane", "Jack"];
  const helloMessages = userList.map(async (user) => {
    return await asyncFunc(user);
  });
  console.log(helloMessages);
};

(() => fetchData())();

正解は、mapの中がasync (user) => {} というように非同期関数になっているからです。

JavaScript Promise - mapの中でawaitした場合

なので、この処理のPromiseをawaitで待つ必要があります。

さらに注意が必要なのが、今回待ちたいのは、誤った処理の出力の通り、Promiseの配列であるということです。

(3) [Promise, Promise, Promise]

このように配列で持っているPromiseを解決するために使用するのが、Promise.all()です。

Promise.all()は、Promiseの集合を1つのPromiseにまとめる処理です。

JavaScript Promise.all イメージ
const asyncFunc = async (user) => {
  return `Hello ${user}`;
};

const fetchData = async () => {
  const userList = ["John", "Jane", "Jack"];
  const helloMessages = await Promise.all(
    userList.map(async (user) => {
      return await asyncFunc(user);
    })
  );
  console.log(helloMessages);
};

(() => fetchData())();

正解の例では、Promise.allによって一つにまとめたPromiseをawaitすることで、値が確定したのですね。

(3) ['Hello John', 'Hello Jane', 'Hello Jack']

まとめ

今回のまとめです。

map処理の中で非同期処理をawaitしたい場合は、以下のように記載を行います。

await Promise.all(
    配列.map(async (…) => {
      await 待ちたい処理
    })
  );

参考文献・サイト

目次