
setTimeout内でのErrorがcatchされない
2022/01/22
いきなりですが、問題です。 ↓のjavascript(typescript)コードを実行した場合、consoleに出力される文言は何になるでしょう?
async function hello() {
try {
setTimeout(() => {
throw new Error('This is timeout Error!!!');
}, 2000);
console.log('Wake up!!! Hello!!!')
} catch (err) {
console.log(err);
}
}
hello();答えはWake up!!! Hello!!!になります。(playground)
setTimeoutは処理を止めるわけではないので、当たり前ですよね。
これはそんなに難しくないと思います。
ではこちらはどうでしょうか?
const sleep = async (time: number) => new Promise((resolve) => setTimeout(resolve, time));
async function hello() {
try {
setTimeout(() => {
throw new Error('This is timeout Error!!!');
}, 2000);
await sleep(5000);
console.log('Wake up!!! Hello!!!')
} catch (err) {
console.log(err);
}
}
hello();playgroundで実行してみると結果はWake up!!! Hello!!!が出力されます。
これ、This is timeout Error!!!が出力されると思った方も多いのではないでしょうか?
少なくとも自分はここを誤解していて、setTimeout内のErrorはcatchされるものだと思いこんでいました。。。
setTimeout内ではsetTimeout外の変数や関数を参照できるので、勘違いしていましたが、setTimeoutのcallbackの実行は元のスコープ(hello関数内部)とは別のスコープで行われる様です。
したがって単純にsetTimeout内でErrorをthrowするだけではhello関数内部でErrorのcatchができないのでしょう。
解決方法
ではこのtimeoutエラーをcatchするにはどうすれば良いでしょう?
方法はいくつかはあると思いますが、例えばPromise.rejectをsetTimeout内部でErrorをthrowする代わりに使用することで解決します。
const sleep = async (time: number) => new Promise((resolve) => setTimeout(resolve, time));
async function hello() {
return new Promise(async (_, reject) => {
setTimeout(() => {
reject(new Error('This is timeout Error!!!'))
}, 2000)
await sleep(5000);
console.log('Wake up!!! Hello!!!')
}).catch((err) => console.log(err))
}
hello();setTimeoutのcallback自体は別スコープで実行されますが、元スコープの関数(ここではreject)を使用することで元スコープでもErrorをcatchできる、ということです。
おわり
非同期は奥が深い
