この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
どうも。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の設定の例は以下です。
.eslintrc.js
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の設定の例は以下です。
.eslintrc.js
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のリンクは以下です。