async await は try catch するよりも単純に catch したほうが良いかも
December 21, 2021
例えば以下の関数があるとします。これは 1秒後にリゾルブまたはリジェクトされるプロミスを返す関数です。どちらになるかは 2 分の 1 でランダムに決まります。
const mayReject = () => {
return new Promise((resolve, reject) => {
const shouldReject = Math.random() >= 0.5;
const settle = shouldReject ? reject : resolve;
const message = shouldReject ? 'rejected' : 'resolved';
setTimeout(() => settle(message), 1000);
});
};
async await
のエラーハンドリングは try catch
で行うのが一般的なので、例えばこの関数を呼び出すときは以下のように書きます。
3回呼び出している mayReject()
のどれかがリジェクトされた場合はその時点で catch
に流れます。
try catch 方式
(async () => {
try {
await mayReject();
await mayReject();
await mayReject();
} catch (e) {
console.error(e);
}
})();
上記で何ら問題はありませんが、以下のように書くこともできます。動きは全く同じです。
catch 方式
(async () => {
await mayReject();
await mayReject();
await mayReject();
})().catch(console.error);
try catch
のブロックがなくなったことにより少しスッキリしました。
1つずつエラーハンドリングするなら catch 方式が断然綺麗 #
それぞれの mayReject()
を個別にエラーハンドリングする場面を想定します。
try catch
では以下の記載になります。
try catch 方式
(async () => {
try {
await mayReject();
} catch (e) {
console.error(e);
}
try {
await mayReject();
} catch (e) {
console.error(e);
}
try {
await mayReject();
} catch (e) {
console.error(e);
}
})();
重たい記述になりました。
一方で単純な catch
を利用すると以下のように書くことができます。
catch 方式
(async () => {
await mayReject().catch(console.error);
await mayReject().catch(console.error);
await mayReject().catch(console.error);
})();
スッキリしています。
catch 方式の別の利点 #
単純な catch
の方では別の利点もあります。
try catch
の場合、try
ブロック内で非同期関数から受け取った結果をブロック外で使う場合は以下のようにする必要があります。
try catch 方式
(async () => {
let res1;
let res2;
let res3;
try {
res1 = await mayReject();
res2 = await mayReject();
res3 = await mayReject();
} catch (e) {
console.error(e);
}
console.info(res1);
console.info(res2);
console.info(res3);
})();
このように、なかなか受け入れ難い書き方にならざるを得ません。
その点、単純な catch
であれば try
ブロックによりスコープを作られることもありませんので、通常通り書くことができます。
catch 方式
(async () => {
const res1 = await mayReject();
const res2 = await mayReject();
const res3 = await mayReject();
console.info(res1);
console.info(res2);
console.info(res3);
})().catch(console.error);
補足:エラーハンドリングの過程が若干異なる #
try catch
方式と catch
方式は同じ動きをするものの、そこまでの過程が若干異なります。
JavaScript の仕様として、await
で待っている Promise
がリゾルブされるとその結果を返しますが、リジェクトされた場合はエラーをスローします。
そのため、try catch
方式がエラーハンドリングする流れは以下です。
try catch 方式
(async () => {
try {
await mayReject(); // 1. リジェクトされたらエラーをスロー
} catch (e) {
console.error(e); // 2. スローされたエラーを catch する
}
})();
このとき await
で呼び出されている関数が try catch
で囲まれていない場合、その親の関数はリジェクトを返します。
ここでいう親の関数とは IIFE (async () => {})();
のことです。
つまり、catch
方式がエラーハンドリングする流れは以下です。
catch 方式
// 2. 内部でエラーがスローされたのでリジェクトを返す
(async () => {
await mayReject(); // 1. リジェクトされたらエラーをスロー
})().catch(console.error); // 3. リジェクトされたことを catch する
おまけ:catch を複数用いた場合 #
最後におまけです。以下の mayReject()
がリジェクトされるとコンソールには何が出力されるでしょうか。
(async () => {
try {
await mayReject().catch(console.error('catched 1'));
} catch {
console.error('catched 2');
}
})();
(async () => {
await mayReject().catch(console.error('catched 1'));
})().catch(() => console.error('catched 2'));
結果はどちらも以下。両方出力されます。
catched 1
catched 2
ではこれはどうでしょう?
(async () => {
try {
await mayReject().catch(console.error('catched 1'));
} catch {
console.error('catched 2');
}
})().catch(() => console.error('catched 3'));
こちらも結果は同様です。catched 3
は出力されません。
catched 1
catched 2