LoginSignup
1
0

More than 3 years have passed since last update.

入れ子のforeach文での非同期処理(async / await)

Posted at

きっかけ

Reduxでコンテンツを管理していて、それぞれのコンテンツについて非同期の通信を行わなければいけなくなったときに、こんがらがったので記事にしておきます。

前提

以下のような関数があるとして、更新後のstateを表示したいとします。

const state = {
  singers: [
    {
      name: 'Alice',
      status: false
    },
    {
      name: 'Bob',
      status: false
    },
  ],
  composers: [
    {
      name: 'InvalidUserName',
      status: false
    },
    {
      name: 'Charlie',
      status: false
    }
  ]
};

// 指定した時間がかかる非同期の関数
const sleep = time => new Promise(resolve => setTimeout(resolve, time));

// Stateの更新
const setStatus = (key, index, bool) => {
  state[key][index].status = bool;
};

// 名前の長さが10以下なら真を返す
const checkName = async (name) => {
  await sleep(500);
  return name.length < 10;
};

ダメな例1(文法エラー)

以下のように書きたいところですが、awaitasync直下にしかかけないので、シンプルにforEach()の中には書けません。

const asyncFunc = async () => {
    Object.keys(state).forEach((key) => {
    state[key].forEach((content, index) => {
      const result = await checkName(content.name); // 文法エラー
      setStatus(key, index, result);
    });
  });

  // stateの表示
  console.log(state);
};

asyncFunc()

ダメな例2(awaitできない)

forEachはasyncで書けるので、以下のようにすればいける気がするのですが、forEachの仕様でawaitしてくれません。

const asyncFunc = async () => {
  await Object.keys(state).forEach(async (key) => {
    await state[key].forEach(async (content, index) => {
      const result = await checkName(content.name);
      setStatus(key, index, result);
    });
  });

  // stateの表示
  console.log(state);
  await sleep(1000);
  console.log(state);
};

asyncFunc();

出力

awaitされず、1回目の出力ではstateが更新されていません。実用ではcheckName()にかかる時間が分からないので、forEach()awaitする必要があります。


{
  singers: [ { name: 'Alice', status: false }, { name: 'Bob', status: false } ],
  composers: [
    { name: 'InvalidUserName', status: false },
    { name: 'Charlie', status: false }
  ]
}
{
  singers: [ { name: 'Alice', status: true }, { name: 'Bob', status: true } ],
  composers: [
    { name: 'InvalidUserName', status: false },
    { name: 'Charlie', status: true }
  ]
}

解決策

Promise.all()という、引数の配列内の全てのPromiseがresolveかrejectされるまでresolveしないという静的メソッドがあるので、それを使用します。Promise.all()で囲うこと、forEach()map()に書き換えるのがポイントです。

const asyncFunc = async () => {
  await Promise.all(Object.keys(state).map(async (key) => {
    await Promise.all(state[key].map(async (content, index) => {
      const result = await checkName(content.name);
      setStatus(key, index, result);
    }));
  }));

  // stateの表示
  console.log(state);
};

asyncFunc();

出力

正常に更新後のstateが出力できています。

{
  singers: [ { name: 'Alice', status: true }, { name: 'Bob', status: true } ],
  composers: [
    { name: 'InvalidUserName', status: false },
    { name: 'Charlie', status: true }
  ]
}

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0