Express 4 はミドルウェア内で async が書けるが、ラッパー関数はあった方が良い
Express 4 系だと、次のように async が書けるようだ。
// async を使用
app.use('/', async (req, res) => {
// await を使用
const result = await something( req.body.id );
res.send(result);
});
ただし上のコードだと、await
部分でエラーが発生した時に next()
が実行されないので、エラーハンドリングミドルウェアに処理が移らない。
キャッチされないエラーが発生するといつまでもレスポンスされず、おかしな動きになる。
次のように書けば、エラー時にエラーハンドリングミドルウェアに処理を流せる。
app.use('/', async (req, res) => {
try {
const result = await something( req.body.id );
res.send(result);
}
catch(error) {
next(error);
}
});
// エラーハンドリングミドルウェア
app.use((error, req, res, next) => {
console.error('Error Handling Middleware');
res.status(500).send(error);
});
しかし、catch
句の変数 error
が undefined
・null
などの空値だと、next()
と同義になり、コレだとエラーハンドリングミドルウェアに処理が移動しない。
例えば以下のようなコードの、★
部分が実行されてしまった場合だ。
app.use('/', async (req, res) => {
try {
const result = await something( req.body.id );
if(result.type === 'failed') {
throw new Error(); // ★
}
else if(result.type === 'rejected') {
await Promise.reject(); // ★
}
res.send(result);
}
catch(error) {
next(error); // error が undefined な値だとエラーハンドリングミドルウェアが呼ばれない
}
});
// エラーハンドリングミドルウェア
app.use((error, req, res, next) => {
console.error('Error Handling Middleware');
res.status(500).send(error);
});
この場合も、いつまでもレスポンスされない動きになる。
catch(error) {
next(error || 'ERROR IS NULL');
}
こんな感じで、error
が空だった時は何らかの値を渡してやれば、とりあえずはなんとかなる。
ところで、ルーティング定義ごとに try / catch
を忘れずに書き、next(error || 'ERROR')
といった処理を書くのは面倒臭い。
そこで、次のようなラッパー関数を作っておくと良いだろう。
function asyncWrap(fn) {
return (req, res, next) => {
return fn(req, res, next)
.catch((error) => {
next(error || 'ERROR IS NULL');
});
};
}
使う時はこんな感じ。
// asyncWrap() 内に async function を書く
app.use('/', asyncWrap(async (req, res) => {
const result = await something( req.body.id );
res.send(result);
}));
// エラーハンドリングミドルウェア
app.use((error, req, res, next) => {
console.error('Error Handling Middleware');
res.status(500).send(error);
});
TypeScript 化しておくとこんな感じのラッパー関数になるだろう。
import * as express from 'express';
/** Promise を返すインターフェース */
interface PromiseRequestHandler {
(req: express.Request, res: express.Response, next: express.NextFunction): Promise<unknown>;
}
/**
* 内部で async を使用できるラッパー関数・例外発生時も次のミドルウェアで処理できるよう next() を呼ぶ
*
* Express 4 からは直接 async ミドルウェアを書けるが、例外を catch し next(error) を明示的に呼ぶ必要があるため、ラッパー関数を用意した
*/
export default function asyncWrap(fn: PromiseRequestHandler): express.RequestHandler {
return (req: express.Request, res: express.Response, next: express.NextFunction): Promise<unknown> => fn(req, res, next)
.catch((error: unknown) => {
next(error || 'ERROR IS NULL'); // 変数 error が null や undefined だとエラーミドルウェアに移動しないので適当な値を入れておく
});
}
以上。