Node.jsのエラーハンドリングの動作をまとめた

JS/TS/Node

Node.jsでエラーハンドリングの実装をする際に、

ネストや非同期処理など色々なケースの動作をまとめました。

基本的なエラーハンドリング

tryブロックでエラーが起きた場合は後続の処理は行われず、catchブロックの処理に移ります。

ログイン中のユーザーを取得して、何かしらの後続処理を行う以下の処理があったとしましょう。

try {
    // ログイン中のユーザーを取得
    const user = getAuthUser()
    if (user === undefined) {
        throw new Error("ログインしてください")
    }
    // 後続処理
} catch (e) {
    console.log(e)
}

ログイン中のユーザーが取得できなかった場合は後続処理を行わず、ログを吐いて終了となります。

try内の関数で起きたエラーはcatchできる

tryブロックで関数を呼び、その関数内でエラーが発生した場合もcatchでエラー内容を取得できます。

tryブロックから呼んだ関数がさらに他の関数を呼んでエラーが起きた場合も同様です。

try {
    getAndSaveUser(id)
} catch (e) {
    console.log(e)
} finally {
    console.log("処理が終了しました")
}
function getAndSaveUser(id) {
    const user = getAuthUser()
    saveUser(user);
}
function saveUser(user) {
    // ここでエラーが発生
}

try内で呼び出された関数であればファイルが違っていたとしてもcatchできます。

ネストしたエラーハンドリングは子のcatchで握り潰す

エラーハンドリングがネストしている場合は期待通りの動きをしているか注意が必要です。

上の処理がネストされていた場合のcatchの動きは以下になります。

// 子のcatch // ①
// エラー内容の詳細 // ②
// 処理が終了しました // ③

子のcatchでエラーを検知した時点で処理が止まり、親のcatchで検知できなくなります。

try {
    getAndSaveUser(id)
} catch (e) {
    console.log("親のcatch")
    console.log(e)
} finally {
    console.log("処理が終了しました") // ③
}
function getAndSaveUser(id) {
    try {
        const user = getAuthUser()
        saveUser(user)
    } catch (e) {
        console.log("子のcatch") // ①
        console.log(e) // ②
    }
}
function saveUser(user) {
    // ここでエラーが発生
}

もし、エラーが起きた際にログを出力するではなく、別の処理も実行しようと実装していた場合、

ネストしたcatchで処理が止まり意図しない動きになる可能性があります。

非同期処理のエラーもcatchできるが書き方に注意が必要

tryブロックで非同期処理を行い、その処理内でエラーが発生した場合もcatchできます。

// 非同期処理 // ①
// catch // ②
// Error: エラー発生 // ③
// 処理が終了しました // ④
(async () => {
    try {
        const result = await asyncFunction()
        console.log(result) // 実行されない
    } catch (e) {
        console.log("catch") // ②
        console.log(e)  // ③
    } finally {
        console.log("処理が終了しました") // ④
    }
})()
// 時間のかかる処理
async function asyncFunction () {
  console.log("非同期処理") //  ①
  // ここでエラーが発生
  throw new Error("エラー発生")
}

しかし非同期処理のエラーハンドリングは間違った書き方をすると、

エラーにはならないので気づきづらいけど適切にcatchできなかったり意図しない動きをします。

例えば以下のようにasyncFunction()を呼び出す時にawaitを書き忘れたり、

(async () => {
    try {
        const result = asyncFunction() // <= awaitを書き忘れた
        console.log(result) // 実行されない
    } catch (e) {
        console.log("catch")
        console.log(e)
    } finally {
        console.log("処理が終了しました")
    }
})()

以下のように例外が発生し得る処理でエラーをthrowし忘れたり、

// 時間のかかる処理
async function asyncFunction () {
  console.log("非同期処理")
  // ここでエラーが発生。<= throwを書き忘れた
}

処理の書き忘れでエラーは発生しないものの、エラーを正しくcatchできなくなるので注意して下さい。

筆者はこれで1日ハマったことがあります。

非同期処理はtry/catchではなくawait/catchでも書ける

上の非同期処理のエラーハンドリングは以下の書き方もできます。

(async () => {
    const result = await asyncFunction()
        .catch((e) => {
            console.log("catch")
            console.log(e)
        })
})()

まとめ

エラーハンドリングは色々な書き方があるので、

実務で使ったり新しく知ったものはどんどん追加していきます。

コメント

タイトルとURLをコピーしました