thumbnail

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!!!になります。(playgroundsetTimeoutは処理を止めるわけではないので、当たり前ですよね。 これはそんなに難しくないと思います。

ではこちらはどうでしょうか?

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.rejectsetTimeout内部で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();

playground

setTimeoutのcallback自体は別スコープで実行されますが、元スコープの関数(ここではreject)を使用することで元スコープでもErrorをcatchできる、ということです。

おわり

非同期は奥が深い

author picture

Mitsuru Takahashi

京都市内にてフリーランスエンジニアとして活動しています。

detail

Profile

author picture

Mitsuru Takahashi

京都市内にてフリーランスエンジニアとして活動しています。

detail

© 2022 mitsuru takahashi