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できる、ということです。
おわり
非同期は奥が深い