きっかけ
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(文法エラー)
以下のように書きたいところですが、await
はasync
直下にしかかけないので、シンプルに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 }
]
}