2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Expressのエラー処理まとめ 【第3弾】非同期関数のエラーハンドラ

Last updated at Posted at 2022-10-17

はじめに

Expressを学び始めて、エラーハンドラの扱い方にいくつかのパターンがあるため、実装方法を整理。
本記事では、第3弾として非同期関数における、エラーハンドラの実装方法をまとめる。

  1. Expressのエラー処理まとめ 【第1弾】Expressのエラーハンミドラの定義
  2. Expressのエラー処理まとめ 【第2弾】バリデーションのミドルウェア
  3. Expressのエラー処理まとめ 【第3弾】非同期関数のエラーハンドラ(本記事)

1.非同期処理のエラーハンドラの実装方法

非同期関数内のエラーについてはExpressの標準のエラーでは取得することができない。非同期関数内のエラーをハンドリングするにはtry-catch構文で括り、ハンドリングする必要がある。

// 非同期処理
const delayedError = (delay) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const num = Math.floor(Math.random() * 10 + 1);
            return resolve(num);
        }, delay);
    });
};

// 非同期関数のエラーハンドリング
app.get('/number1', async (req, res, next) => {
    try {
        const num = await delayedError(1000);
            if (num > 5) { //6以上だったら正常
            res.send(`非同期処理成功:${num}`);
        } else { //5以下だったらエラー
            throw new AppError(`非同期処理失敗:${num}`, 401);
        }
    } catch (e) {
        next(e);
    }
});

処理の結果、非同期処理の関数内でエラーが発生した場合にエラーのハンドリングができていることが分かる。
image.png

ちなみに、try-cachを外して実行してみると

app.get('/number1', async (req, res, next) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
});

エラー発生時にハンドリングができていないことがわかる。
image.png

ということで、やはり非同期関数内のエラーハンドリングはtry-catch構文で拾ってあげる必要があることが分かった。

2.非同期関数内のエラーを取得するwrap関数を用意する

非同期関数をエラーハンドリングするために、try-catch構文で括る必要があることが分かったが、非同期関数は頻繁に記載するため、毎回try-catch構文で括るのは結構手間。そこで推奨されている方法が、非同期関数のエラーをハンドリングできる関数を1つ用意し(wrap関数という)、非同期関数を引数として渡してあげることで、その非同期関数のエラーをハンドリングする方法だ。まずは結論のコードから書いてみる。

// wrap関数
function wrapAsync(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch(e => next(e));
    }
}

// 非同期処理
app.get('/number1', wrapAsync(async (req, res) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}));

処理結果も確認すると、確かにエラーのハンドリングができていることがわかる。
image.png

だけど、このwrap関数、コードの量が少ない割に、これがなぜ非同期関数をtry-catch構文で括った関数と同じになるのか理解することが難解。わかりそうで分かない...。そのため、次項から少しずつコードを変形しながら考えてみる。

3.非同期処理wrap関数の変形

STEP1

初期状態(非同期関数がtry-catch構文で括られている状態。)

app.get('/number1', async (req, res, next) => {
    try {
        const num = await delayedError(1000);
        if (num > 5) {
            res.send(`非同期処理成功:${num}`);
        } else {
            throw new AppError(`非同期処理失敗:${num}`, 401);
        }
    } catch (e) {
        next(e);
    }
});

STEP2

非同期関数をごっそり引き抜き、ルートハンドラのコールバック関数を返す関数を定義する。

function wrapAsync2() {
    return async (req, res, next) => {
        try {
            const num = await delayedError(1000);
            if (num > 5) {
                res.send(`非同期処理成功:${num}`);
            } else {
                throw new AppError(`非同期処理失敗:${num}`, 401);
            }
        } catch (e) {
            next(e);
        }
    }
}

app.get('/number2', wrapAsync2());

STEP3

try-catch構文内で実行したい非同期関数をfnで定義して抜き出し、wrap関数の引数に渡す。ルートハンドラで得た(req,res,next)をfnの引数に渡すために、return functoion(req,res,net){}の関数内でfnを実行する。またfnは非同期処理でPromiseを返す関数のため、エラー発生時は「.catch」としてエラーをハンドリングすることができる。

const fn = async function (req, res, next) {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}

function wrapAsync3(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch((e) => { //非同期処理の例外はPromiseの戻り値Rejetとしてchatcheへ
            next(e)
        })
    }
}

app.get('/number3', wrapAsync3(fn));

STEP4

実行したい非同期関数fnをルートハンドラに入れる。

function wrapAsync4(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch((e) => {
            next(e)
        })
    }
}

app.get('/number4', wrapAsync4(async (req, res, next) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}));

STEP5

成形する。
Promiseの構文を簡略化し、ルートハンドラの関数ではnextを利用していないため削除。nextは暗黙的に保持しており、使用がなければ削除できる。

function wrapAsync4(fn) {
    return function (req, res, next) {
        fn(req, res, next).catch(e => next(e));
    }
}

app.get('/number5', wrapAsync4(async (req, res) => {
    const num = await delayedError(1000);
    if (num > 5) {
        res.send(`非同期処理成功:${num}`);
    } else {
        throw new AppError(`非同期処理失敗:${num}`, 401);
    }
}));

まとめ

非同期関数のエラーハンドラーはtry-catch構文を利用するすことで、実装できる。非同期関数を受取り、try-catch構文を1つ作ることで、他の非同期関数のエラーハンドリングに活用できる。DBを伴う処理などは非同期処理となるため、このようなエラーハンドリグを実装する必要があることが分かった。
エラー処理のまとめ全3弾終わり。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?