Express.jsでasync/awiatをいい感じに使う
はじめに
お久しぶりです。
最近は業務でTypeScriptを書き始めたりしてました。
今日は、会社のテックブログに載せるつもりで書いてみたけど、内容的に会社のブログで載せるほどの内容にならず、ボツにした記事をここで供養しようと思いますw
ちなみに、会社のテックブログに書いた内容はこちら 👇
https://cam-inc.co.jp/p/techblog/407753782164718758
Express.jsのTIPS紹介
ということで今日は、Express.jsのtipsを1つ紹介します。
Express.jsとは
まず簡単にExpress.jsの説明です。
Express.jsは、Node.js製の薄いWebアプリケーションフレームワークです。
https://expressjs.com/ja/
とりあえず、これだけ書けばサーバーを動かすことができます。
const express = require('express'); const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(port, () => console.log('started server'));
上記コードだと、 /
というエンドポイントでGETメソッドのリクエストを受け取ると、5行目の関数のコールバックメソッドが呼ばれて、 Hello World!
という文字列がリクエストのレスポンスとして送られてきます。
Express.jsでの非同期処理
先ほどの例では、リクエストが来た時に文字列を返すという同期的な処理しかありませんでした。しかし、実際のアプリケーションではDBに問い合わせたり、別のアプリケーションと通信をしたりなど、非同期的な処理が発生すると思います。
Node.jsでの非同期処理のアプローチには大きく分けて、コールバックパターンとPromiseを使ったパターンがあると思いますが、最近はPromiseで非同期処理を扱うことの方が多いと思いますので、今回はPromiseを使った例のみ扱います。
// ユーザー一覧を返す app.get('/users', (req, res, next) => { // DBに問い合わせてUser一覧を取得して返す。 Users .find() .then(users => { res.json(users); }) .catch(error => { next(error); }); }); // ExpressにビルトインされているError Handlerのmiddleware // next()にエラーを渡すことで、このmiddlewareが呼ばれる app.use((err, req, res) => { console.error(err); res.status(500).send('Internal Server Error'); // ステータスコード500でレスポンスを返す });
上記のコードは擬似的なコードですが、 /users
というエンドポイントをGETでリクエストすると、非同期処理としてDBに問い合わせてuser一覧を取得してレスポンスとして返しています。
最後の4行は、Express.jsにビルトインされているエラーハンドラーのミドルウェアで、発生したエラーをここで処理します。
ここで大事なのは、 Users.find()
の最後に catch
でエラーをハンドルして next
に渡していることです。Express.jsは同期処理で発生するエラーはキャッチして扱ってくれますが、非同期処理に関してはよしなに扱ってくれません。
Promiseを返す関数を使う場合は、catchでエラーを受け取り next
に渡さないと非同期処理内でエラーが発生した場合に、エラーがハンドルできません。
async/awaitの場合
先ほどはPromiseを使って非同期処理を書きましたが、次はPromiseをより便利に扱う async/await
を使った書き方です。
app.get('/users', async (req, res, next) => { // DBに問い合わせてUser一覧を取得して返す。 try { const result = await Users.find(); res.json(result); } catch (error) { next(error); } }); // error handlerの処理は同様なので省略
注意すべき点は、 async/awat
も非同期処理なので、try/catch
でエラーをcatchして next
にエラーを渡してやる必要がある点です。
tips
async/await
は then
の呼び出しがないため、同期処理と同じようにネストせずに書ける便利な構文ですが、上の例で書いたようにExpress.jsのハンドラーで使うには、必ず最初に try/catch
が必要になってしまいます。
そこで今回のTIPSの紹介です。
以下のような async/await
をシンプルに書けるようにwrapper関数を定義します。
const asyncWrapper = fn => { return (req, res, next) => { return fn(req, res, next).catch(next); } }; app.get('/users', asyncWrapper(async (req, res, next) => { // DBに問い合わせてUser一覧を取得して返す。 const result = await Users.find(); res.json(result); })); // error handlerの処理は同様なので省略
最初の行で定義している asyncWrapper
は、引数に AsyncFunction
を受け取り、戻り値としてExpress.jsのハンドラーを返しています。そして、実際にリクエストがあった際にハンドラー内部では、 AsyncFunction
である引数 fn
を実行しており、 catch
でエラーをハンドルして、 next
にエラーを渡しています。
リクエストハンドラーで使う際は、 async/await
の例で書いたハンドラーを asyncWrapper
の引数として渡します。最初の例で書いていたコードでは、ハンドラー内部で try/catch
を書いていましたが、その役割は asyncWrapper
が引き受けてくれているので、ハンドラー内部で try/catch
を書かなくてもよくなり、コードがスッキリしました。
Express.js 5では
最後にExpress.js 5ではどうなりそうかについてです。
Express.js 5からは、今までExpress.js内で書かれていたコードは、 pillarjs
というprojectのコンポーネントに移行しています。それと合わせてHTTP/2とPromiseを返すハンドラのサポートもしているようです。
今までのルーティングやハンドラのmoduleは、 pillarjs
の router
というリポジトリに移行されており、v2.0.0-beta.1 からpromiseを返すことが可能になっています。
https://github.com/pillarjs/router
まだ先になりそうですが、Express.js 5からは asyncWrapper
のようなコードは不要になるかもしれませんね。
まとめ
今回は、Express.js のハンドラーで非同期処理を扱い方とtipsを紹介しました。
ボツにした記事も供養できて満足です笑
またいいネタがあれば記事にしたいと思います。