async/await周りで苦しんだ箇所の例と対策
どうも。CX事業本部Delivery部のえーたん(@eetann092)です。
今まで雰囲気でJavaScript(TypeScript)のasync/await、Promiseを使っていて最近苦しんだため、ハマったところの例と対策を備忘録として残しておきます。
await付け忘れ
やらかした例
まず、await
を付け忘れた例です。
import { setTimeout } from "timers/promises"; const unit = 1000; async function logSleepLog(msg: string) { console.log(msg); await setTimeout(1.5 * unit); console.log(msg); } (async () => { logSleepLog("hoo"); console.log("finish"); })();
hoo finish hoo
書き直した例
次に、awaitを付けた例です。
import { setTimeout } from "timers/promises"; const unit = 1000; async function logSleepLog(msg: string) { console.log(msg); await setTimeout(1.5 * unit); console.log(msg); } (async () => { await logSleepLog("hoo"); console.log("finish"); })();
hoo hoo finish
対策
@typescript-eslint/eslint-pluginには、awaitの書き忘れを防ぐためのルールno-floating-promises
があります。
今回はIIFE(Immediately Invoked Function Expression 即時実行関数式)を使っているため、IIFEでは怒られないようにignoreIIFE
をtrue
にします。
eslintの設定の例は以下です。
rules: { "@typescript-eslint/no-floating-promises": [ "warn", { ignoreIIFE: true } ],
awaitを付け忘れると、以下のようにエラーで怒ってくれます。
Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the
void
operator. [@typescript-eslint/no-floating-promises]
forEachの中でasync/await
やらかした例
まず、forEachの中でasync/awaitを書いた例です。
import { setTimeout } from "timers/promises"; const unit = 1000; async function logSleepLog(msg: string) { console.log(msg); await setTimeout(1.5 * unit); console.log(msg); } (async () => { const foo = [1, 2]; foo.forEach(async (f) => { await logSleepLog(`${f}`); }); console.log("finish"); })();
1 2 finish 1 2
書き直した例
次に、for ofを使って書き直した例です。以下の記事を参考にしました。
import { setTimeout } from "timers/promises"; const unit = 1000; async function logSleepLog(msg: string) { console.log(msg); await setTimeout(1.5 * unit); console.log(msg); } (async () => { const foo = [1, 2]; for (const f of foo) { await logSleepLog(`${f}`); } console.log("finish"); })();
1 1 2 2 finish
対策
@typescript-eslint/eslint-pluginには、async/awaitを使うべきではない書き方を怒ってくれるルールno-misused-promises
があります。
eslintの設定の例は以下です。
rules: { "@typescript-eslint/no-floating-promises": ["warn", { ignoreIIFE: true }], "@typescript-eslint/no-misused-promises": "warn",
async/awaitを使うべきではない書き方では、以下のようにエラーで怒ってくれます。
Promise returned in function argument where a void return was expected. [@typescript-eslint/no-misused-promises]
Promise.allはすべてのエラーをcatchするわけではない
勘違いした例
Promise.all
はすべてのエラーをcatchするわけではないようです。
以下、Promise.all
と4つのエラーを起こす例です。
import { setTimeout } from "timers/promises"; const unit = 1000; async function hoge(num:number, msg:string) { const a = 'hoge'; await setTimeout(num * unit); if (a===msg) { console.log(`正常 ${num}:${msg}`) } else { console.log(`エラーが起きた ${num}:${msg}`) throw `throwのメッセージ ${num}:${msg}` } } (async () => { const promises = []; promises.push(hoge(1, 'hoge')); // 1秒後に 正常 promises.push(hoge(2, 'foo')); // 2秒後に エラー promises.push(hoge(5, 'bar')); // 5秒後に エラー promises.push(hoge(4, 'bar')); // 4秒後に エラー promises.push(hoge(3, 'bar')); // 3秒後に エラー await Promise.all(promises).catch(e => console.log(e)) console.log("finish"); })();
以下が出力です。catchされたエラーは最初の2
のみです。また、Promise.all
では、catchが終わった時点で次の行("finish"の表示)の処理に移るようです。
正常 1:hoge エラーが起きた 2:foo throwのメッセージ 2:foo finish エラーが起きた 3:bar エラーが起きた 4:bar エラーが起きた 5:bar
書き直した例
Promise.allSettled
を使えば、すべてのプロミスが終わってから結果を返してくれます。
ちなみに "settled" は「確立した、定まった」などの意味を持つ単語のようです(参考:英語「settled」の意味・読み方・表現 | Weblio英和辞書)。
以下が、Promise.allSettled
を使って書き直した例です。
成功した場合、受け取った結果のstatus
は"fulfilled"
になり、value
で値を取得できます。
失敗した場合、受け取った結果のstatus
は"rejected"
になり、reason
で理由を取得できます。
import { setTimeout } from "timers/promises"; const unit = 1000; async function hoge(num: number, msg: string) { const a = "hoge"; await setTimeout(num * unit); if (a === msg) { console.log(`正常 ${num}:${msg}`); } else { console.log(`エラーが起きた ${num}:${msg}`); throw `throwのメッセージ ${num}:${msg}`; } } (async () => { const promises = []; promises.push(hoge(1, "hoge")); // 1秒後に 正常 promises.push(hoge(2, "foo")); // 2秒後に エラー promises.push(hoge(5, "bar")); // 5秒後に エラー promises.push(hoge(4, "bar")); // 4秒後に エラー promises.push(hoge(3, "bar")); // 3秒後に エラー await Promise.allSettled(promises).then((results) => results.map((result) => { if (result.status === "rejected") { console.log(result.reason); } }) ); console.log("finish"); })();
以下が出力です。すべての実行が終わってからエラーの内容を吐き出しています。
正常 1:hoge エラーが起きた 2:foo エラーが起きた 3:bar エラーが起きた 4:bar エラーが起きた 5:bar throwのメッセージ 2:foo throwのメッセージ 5:bar throwのメッセージ 4:bar throwのメッセージ 3:bar finish
参考:
リンク集
サンプルコードのGitHubのリンクは以下です。