ishikawa_pro's memorandum

若手webエンジニアの備忘録です.

Swift Node.js Docker AWS etc...色々やります。

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/awaitthen の呼び出しがないため、同期処理と同じようにネストせずに書ける便利な構文ですが、上の例で書いたように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を返すハンドラのサポートもしているようです。

Express.js Release 5.0

今までのルーティングやハンドラのmoduleは、 pillarjsrouter というリポジトリに移行されており、v2.0.0-beta.1 からpromiseを返すことが可能になっています。
https://github.com/pillarjs/router
まだ先になりそうですが、Express.js 5からは asyncWrapper のようなコードは不要になるかもしれませんね。

まとめ

今回は、Express.js のハンドラーで非同期処理を扱い方とtipsを紹介しました。
ボツにした記事も供養できて満足です笑
またいいネタがあれば記事にしたいと思います。