Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

Express.jsのmiddlewareでasync/await関数が動かない時の解決法

記事のサムネイル
TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

Express.jsのasync/await問題とは

Express.jsでasync/await構文を使用したミドルウェア関数を実装すると、特定の状況下で予期せぬ動作に悩まされることがあります。特に、非同期処理中に発生した例外がExpress.jsのエラーハンドリングミドルウェアにキャッチされないという問題です。

この問題は一見シンプルなように見えますが、実際のプロジェクトでは深刻なバグを引き起こす可能性があります。特に以下のような状況でこの問題に直面することが多いです:

  • データベースやAPIへの非同期アクセスを含むミドルウェア
  • 認証・認可の処理を行うミドルウェア
  • ファイル操作などのI/O処理を含むミドルウェア

Node.jsの非同期プログラミングモデルとExpressのミドルウェアアーキテクチャの相互作用から生じるこの問題は、多くの開発者が一度は遭遇する典型的な落とし穴です。

基本的なExpressのミドルウェアは以下のように実装されます:

app.use((req, res, next) => {
  // ミドルウェアの処理
  next();
});

しかし、非同期処理を含む場合、以下のように実装すると問題が発生します:

app.use(async (req, res, next) => {
  try {
    // 何らかの非同期処理
    await someAsyncOperation();
    next();
  } catch (error) {
    // ここでcatchしても、エラーハンドリングミドルウェアに伝播しない場合がある
    next(error);
  }
});

この記事では、この問題が発生する原因を深く掘り下げ、効果的な解決策を紹介します。複数のアプローチを比較検討し、それぞれの利点と欠点も解説します。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

発生する具体的なエラーパターン

この問題が具体的にどのような症状として現れるのか、実際のコード例で見てみましょう。以下は、典型的なExpress.jsのアプリケーションでasync/awaitを使用したミドルウェアを実装した例です。

const express = require('express');
const app = express();

// 非同期ミドルウェア - 問題を含む実装
app.use(async (req, res, next) => {
  try {
    // データベースアクセスをシミュレート
    await new Promise((resolve, reject) => {
      setTimeout(() => {
        // 何らかの理由でエラーが発生
        reject(new Error('データベース接続エラー'));
      }, 100);
    });
    
    next();
  } catch (error) {
    // このエラーはキャッチされるが、後続のエラーハンドラーに伝播されない場合がある
    next(error);
  }
});

// ルートハンドラー
app.get('/', (req, res) => {
  res.send('Hello World!');
});

// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
  console.error('エラーハンドラーが呼ばれました:', err.message);
  res.status(500).send('サーバーエラーが発生しました');
});

app.listen(3000, () => {
  console.log('サーバーが起動しました');
});

上記のコードを実行すると、以下のようなエラーパターンが発生することがあります:

  1. 未処理のPromise拒否警告:ミドルウェア内で発生したエラーが適切に処理されず、Node.jsから以下のような警告が表示されます。
(node:12345) UnhandledPromiseRejectionWarning: Error: データベース接続エラー
    at Timeout._onTimeout (/path/to/your/app.js:10:18)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
(node:12345) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch().
  1. エラーハンドラーが呼ばれないnext(error)を呼び出しているにもかかわらず、エラーハンドリングミドルウェアに制御が移らず、クライアントに適切なエラーレスポンスが返されません。これは、クライアント側ではリクエストがタイムアウトする可能性があります。

  2. サーバーがハングする:最悪の場合、未処理の例外によってリクエスト処理が中断され、サーバーが応答しなくなる可能性があります。

これらの症状は、開発環境ではすぐに気付くかもしれませんが、本番環境では検出が難しく、サーバーの信頼性に深刻な影響を与える可能性があります。

特に以下のシナリオで問題が発生しやすくなります:

  • 複数の非同期ミドルウェアを連鎖させている場合
  • サードパーティのライブラリやAPIを呼び出している場合
  • 大量のリクエストを処理するサーバーで、断続的にエラーが発生する場合

次のセクションでは、この問題が発生する根本的な原因について説明します。

あわせて読みたい

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

非同期ミドルウェアのエラーが伝播しない理由

なぜasync/awaitを使用したExpressミドルウェアでエラーが正しく伝播しないのでしょうか?この問題を理解するために、Express.jsのミドルウェアアーキテクチャとJavaScriptの非同期処理モデルの関係を詳しく見ていきましょう。

Node.jsのイベントループとPromiseチェーン

Node.jsはシングルスレッドのイベントループモデルを採用しています。非同期操作は、コールバック、Promise、async/await構文を使用して処理されます。

async/await構文を使用した関数は、内部的にはPromiseを返します。例えば:

async function myAsyncFunction() {
  // 何らかの非同期処理
  return 'result';
}

// これは以下と同等
function myPromiseFunction() {
  return Promise.resolve('result');
}

Express.jsのミドルウェア実行モデル

Express.jsは、リクエストを処理するために「ミドルウェアチェーン」という概念を使用します。各ミドルウェア関数は、next()を呼び出すことで次のミドルウェアに制御を渡します。

function middleware1(req, res, next) {
  // 処理
  next(); // 次のミドルウェアに制御を渡す
}

function middleware2(req, res, next) {
  // 処理
  next(); // 次のミドルウェアに制御を渡す
}

問題の核心部分

Express.jsは、非同期ミドルウェア関数から返されるPromiseを自動的に処理するメカニズムを持っていません。これが問題の根本原因です。

具体的には:

  1. ミドルウェア関数がasync関数として宣言されると、その関数は暗黙的にPromiseを返します。
  2. Express.jsのミドルウェア実行エンジンは、この返されたPromiseを使用してエラーをキャッチするように設計されていません。
  3. そのため、try/catchブロック内でnext(error)を呼び出しても、既に非同期コンテキストに入っているため、Expressのエラーハンドリングメカニズムに適切にエラーが渡されない場合があります。

これを図示すると以下のようになります:

リクエスト
   │
   ↓
async ミドルウェア ──→ Promise返却
   │                  │
   │ (エラー発生)      │
   ↓                  │
next(error)           │
   │                  │
   │                  │
Expressエラーハンドラ  │
   ↑                  │
   └──────────────────┘
     (接続が切れている)

コード例で見る問題

以下のコードは、なぜこの問題が発生するかを示しています:

app.use(async (req, res, next) => {
  // 非同期処理の開始 - 新しいPromiseコンテキストが作成される
  try {
    await someAsyncOperation(); // エラーが発生
    next();
  } catch (error) {
    // エラーをキャッチして次に渡す
    next(error);
    // しかし、この時点で既にExpressのミドルウェアチェーンと
    // このasync関数が返すPromiseの間に「切断」が発生している
  }
  // async関数の実行が終了し、Promiseが解決または拒否される
  // Expressはこの返されたPromiseを監視していないため、
  // エラーが適切に伝播しない
});

Express.jsがこの問題を持つ理由

Express.jsは2010年に最初にリリースされ、その基本設計はES6のPromiseやasync/awaitが広く採用される前に確立されました。そのため、Expressのコアアーキテクチャは、主にコールバックベースの非同期処理モデルを中心に設計されています。

Express 5.0ではこの問題に対応する予定がありますが、現時点では開発者がこの問題を回避するための対策を実装する必要があります。次のセクションでは、この問題を解決するための実践的なアプローチを紹介します。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

問題を解決する3つの実装パターン

Express.jsで非同期ミドルウェアのエラーハンドリング問題を解決するための主要なアプローチが3つあります。それぞれの実装パターンについて、コード例とともに詳しく見ていきましょう。

1. ラッパー関数を使用する方法

最も一般的なアプローチの1つは、async/await関数をラップするユーティリティ関数を作成することです。このラッパーは、非同期関数がPromiseを返す場合に、そのPromiseをキャッチしてエラーをnext()に渡す役割を担います。

// 非同期ミドルウェアをラップするユーティリティ関数
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// 使用例
app.use(asyncHandler(async (req, res, next) => {
  // データベースアクセスなどの非同期処理
  const data = await db.getData();
  res.locals.data = data;
  next();
}));

長所:

  • シンプルで理解しやすい実装
  • 既存のコードベースに簡単に統合できる
  • 各ミドルウェア関数で個別に適用できる

短所:

  • すべての非同期ミドルウェアに手動でラッパーを適用する必要がある
  • コードの冗長性を増やす可能性がある

2. サードパーティのミドルウェアライブラリを使用する方法

このアプローチでは、既存のライブラリを活用してこの問題を解決します。express-async-errorsexpress-async-handlerなどのライブラリは、この問題に特化したソリューションを提供しています。

// express-async-errorsライブラリを使用する例
const express = require('express');
require('express-async-errors'); // グローバルにエラーハンドリングを有効化

const app = express();

// express-async-errorsを導入した後は通常のasync/awaitが正しく動作する
app.get('/data', async (req, res) => {
  // エラーが発生した場合、自動的にエラーハンドラーにキャッチされる
  const data = await db.getData();
  res.json(data);
});

// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send('サーバーエラーが発生しました');
});

あるいは、express-async-handlerを使用する場合:

const express = require('express');
const asyncHandler = require('express-async-handler');
const app = express();

// asyncHandlerでラップする
app.get('/data', asyncHandler(async (req, res) => {
  const data = await db.getData();
  res.json(data);
}));

// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).send('サーバーエラーが発生しました');
});

長所:

  • 完成度の高いソリューションを簡単に導入できる
  • コミュニティでテストされ、信頼性が高い
  • 最小限のコード変更で問題を解決できる

短所:

  • 外部依存関係の追加
  • ライブラリの実装に依存するため、完全な制御はできない

3. ルーターレベルでのミドルウェアパッチ適用方法

もう一つの方法は、Express.jsのルーターをモンキーパッチングして、すべてのルートハンドラーとミドルウェアを自動的にラップする方法です。

// ルーターレベルでのパッチ適用
const express = require('express');
const app = express();

// Expressのルーターをモンキーパッチ
const originalUse = express.Router.use;
express.Router.use = function() {
  // 引数を処理
  for (let i = 0; i < arguments.length; i++) {
    const handler = arguments[i];
    if (typeof handler === 'function' && handler.constructor.name === 'AsyncFunction') {
      arguments[i] = (req, res, next) => {
        Promise.resolve(handler(req, res, next)).catch(next);
      };
    }
  }
  return originalUse.apply(this, arguments);
};

// 同様にして、get, post, put, deleteなどのメソッドもパッチする
const methods = ['get', 'post', 'put', 'delete', 'patch'];
methods.forEach(method => {
  const originalMethod = express.Router[method];
  express.Router[method] = function() {
    // パスを飛ばして、ハンドラーのみを処理
    for (let i = 1; i < arguments.length; i++) {
      const handler = arguments[i];
      if (typeof handler === 'function' && handler.constructor.name === 'AsyncFunction') {
        arguments[i] = (req, res, next) => {
          Promise.resolve(handler(req, res, next)).catch(next);
        };
      }
    }
    return originalMethod.apply(this, arguments);
  };
});

// これ以降、すべての非同期ミドルウェアが自動的にラップされる
app.get('/data', async (req, res) => {
  const data = await db.getData();
  res.json(data);
  // エラーが発生した場合は自動的にキャッチされる
});

長所:

  • コードベース全体で一貫した処理が可能
  • 既存のミドルウェアを変更せずに問題を解決できる
  • 特定のパターンやコード規約に合わせてカスタマイズできる

短所:

  • モンキーパッチングはExpress.jsの内部実装に依存するため、将来のバージョンで動作しなくなる可能性がある
  • 複雑な実装が必要
  • デバッグが難しくなる場合がある

各アプローチにはそれぞれ長所と短所がありますが、次のセクションでは、実際のプロジェクトに最も適した推奨ソリューションを詳しく説明します。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

関連記事

推奨される解決策と実装例

前のセクションでは3つの実装パターンを紹介しましたが、この中で最も推奨される方法は、カスタムのasyncHandlerユーティリティを使用する方法です。この手法は、理解しやすく、コードベースの保守性を高め、外部ライブラリへの依存を最小限に抑えることができます。

カスタムasyncHandlerの実装と応用例

以下の実装例は、小規模から中規模のプロジェクトに適した、シンプルかつ効果的なasyncHandlerの実装です:

// utils/async-handler.js
/**
 * 非同期ミドルウェアをラップし、エラーを次のミドルウェアに伝播させるユーティリティ関数
 * @param {Function} fn - 非同期ミドルウェア関数
 * @returns {Function} Expressミドルウェア関数
 */
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

module.exports = asyncHandler;

このユーティリティ関数は、次のように様々なパターンで利用できます:

1. 標準的なミドルウェアでの使用例

const express = require('express');
const asyncHandler = require('./utils/async-handler');
const app = express();

// ユーザー認証ミドルウェアの例
app.use(asyncHandler(async (req, res, next) => {
  // JWTトークンの検証などの非同期処理
  const user = await verifyToken(req.headers.authorization);
  req.user = user;
  next();
}));

// ルートハンドラーの例
app.get('/api/profile', asyncHandler(async (req, res) => {
  const profile = await getUserProfile(req.user.id);
  res.json(profile);
}));

// エラーハンドラー
app.use((err, req, res, next) => {
  console.error('エラーハンドリングミドルウェア:', err);
  res.status(err.status || 500).json({
    error: {
      message: err.message || 'サーバーエラーが発生しました',
      code: err.code || 'SERVER_ERROR'
    }
  });
});

2. ルーターでの使用例

// routes/users.js
const express = require('express');
const router = express.Router();
const asyncHandler = require('../utils/async-handler');
const { User } = require('../models');

// すべてのユーザーを取得
router.get('/', asyncHandler(async (req, res) => {
  const users = await User.findAll();
  res.json(users);
}));

// 特定のユーザーを取得
router.get('/:id', asyncHandler(async (req, res) => {
  const user = await User.findByPk(req.params.id);
  if (!user) {
    const error = new Error('ユーザーが見つかりません');
    error.status = 404;
    throw error; // このエラーは適切にキャッチされる
  }
  res.json(user);
}));

module.exports = router;

3. コントローラーパターンでの使用例

MVCパターンを使用するプロジェクトでは、コントローラー関数と組み合わせて使用できます:

// controllers/user-controller.js
const { User } = require('../models');

// コントローラー関数
exports.getUsers = async (req, res) => {
  const users = await User.findAll();
  res.json(users);
};

exports.getUserById = async (req, res) => {
  const user = await User.findByPk(req.params.id);
  if (!user) {
    const error = new Error('ユーザーが見つかりません');
    error.status = 404;
    throw error;
  }
  res.json(user);
};

// routes/users.js
const express = require('express');
const router = express.Router();
const asyncHandler = require('../utils/async-handler');
const userController = require('../controllers/user-controller');

router.get('/', asyncHandler(userController.getUsers));
router.get('/:id', asyncHandler(userController.getUserById));

module.exports = router;

高度なエラーハンドリングの実装

より高度なプロジェクトでは、エラーハンドリングをさらに拡張して、異なるタイプのエラーに対して適切なレスポンスを返すことができます:

// utils/error-handler.js
// カスタムエラークラス
class AppError extends Error {
  constructor(message, statusCode, errorCode) {
    super(message);
    this.statusCode = statusCode;
    this.errorCode = errorCode;
    this.isOperational = true; // 運用上のエラーとして識別
    Error.captureStackTrace(this, this.constructor);
  }
}

// 非同期ミドルウェアラッパー
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// グローバルエラーハンドラーミドルウェア
const errorHandler = (err, req, res, next) => {
  // ログ出力(開発環境ではスタックトレースを含む)
  console.error(err.stack);
  
  // エラーのタイプによって異なるレスポンスを返す
  if (err.isOperational) {
    // 運用上の既知のエラー
    return res.status(err.statusCode).json({
      error: {
        message: err.message,
        code: err.errorCode
      }
    });
  }
  
  // 予期しないエラー
  res.status(500).json({
    error: {
      message: '予期しないエラーが発生しました',
      code: 'INTERNAL_SERVER_ERROR'
    }
  });
};

module.exports = {
  AppError,
  asyncHandler,
  errorHandler
};

使用例:

const express = require('express');
const { asyncHandler, errorHandler, AppError } = require('./utils/error-handler');
const app = express();

app.get('/api/users/:id', asyncHandler(async (req, res) => {
  const user = await getUserById(req.params.id);
  if (!user) {
    // カスタムエラーを投げる
    throw new AppError('ユーザーが見つかりません', 404, 'USER_NOT_FOUND');
  }
  res.json(user);
}));

// エラーハンドラーを最後に配置
app.use(errorHandler);

推奨される解決策を選ぶ理由

カスタムのasyncHandlerを使用する方法を推奨する理由は以下のとおりです:

  1. シンプルさと透明性: たった数行のコードで問題を解決できます。これにより、チームメンバーが実装を理解しやすくなります。

  2. 明示的な使用: 非同期ミドルウェアを明示的にラップすることで、どのミドルウェアが非同期処理を含んでいるかが一目でわかります。

  3. 柔軟性: プロジェクトの要件に合わせてエラーハンドリングロジックをカスタマイズできます。

  4. テスト容易性: シンプルな実装であるため、ユニットテストが容易です。

  5. 依存関係の最小化: 外部ライブラリに依存せず、自前の実装を使用することで、依存関係の問題を回避できます。

カスタムasyncHandlerを採用することで、Express.jsでの非同期エラーハンドリングの問題を効果的に解決しながら、コードベースの保守性と可読性を高めることができます。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

統合テストによる検証方法

非同期ミドルウェアのエラーハンドリングが正しく動作することを確認するために、統合テストを作成することも重要です。ここでは、Jest と Supertest を使用したテスト例を紹介します。

テスト環境のセットアップ

まず、必要なテストライブラリをインストールします:

npm install --save-dev jest supertest

テストケースの作成

以下は、asyncHandlerを使用したExpressアプリケーションの統合テスト例です:

// __tests__/async-error-handling.test.js
const request = require('supertest');
const express = require('express');
const asyncHandler = require('../utils/async-handler');

describe('Express.js 非同期エラーハンドリングテスト', () => {
  let app;

  // 各テストケースの前にExpressアプリを初期化
  beforeEach(() => {
    app = express();
    
    // 非同期ミドルウェアを設定
    app.get('/success', asyncHandler(async (req, res) => {
      const data = await Promise.resolve({ message: '成功' });
      res.json(data);
    }));
    
    // エラーを発生させる非同期ミドルウェア
    app.get('/error', asyncHandler(async (req, res) => {
      // 意図的にエラーを発生させる
      await Promise.reject(new Error('テストエラー'));
      res.json({ message: 'ここには到達しません' });
    }));
    
    // カスタムエラーオブジェクトを投げるミドルウェア
    app.get('/custom-error', asyncHandler(async (req, res) => {
      const error = new Error('カスタムエラー');
      error.status = 400;
      error.code = 'BAD_REQUEST';
      throw error;
    }));
    
    // エラーハンドリングミドルウェア
    app.use((err, req, res, next) => {
      res.status(err.status || 500).json({
        error: {
          message: err.message,
          code: err.code || 'SERVER_ERROR'
        }
      });
    });
  });

  test('成功ケース: 非同期処理が正常に完了する', async () => {
    const response = await request(app).get('/success');
    
    expect(response.status).toBe(200);
    expect(response.body).toEqual({ message: '成功' });
  });

  test('エラーケース: 非同期処理で例外が発生する', async () => {
    const response = await request(app).get('/error');
    
    expect(response.status).toBe(500);
    expect(response.body.error).toBeDefined();
    expect(response.body.error.message).toBe('テストエラー');
  });

  test('カスタムエラーケース: ステータスコードとエラーコードが正しく設定される', async () => {
    const response = await request(app).get('/custom-error');
    
    expect(response.status).toBe(400);
    expect(response.body.error).toBeDefined();
    expect(response.body.error.message).toBe('カスタムエラー');
    expect(response.body.error.code).toBe('BAD_REQUEST');
  });
});

パフォーマンステスト

より複雑なプロジェクトでは、非同期エラーハンドリングのパフォーマンスも考慮する必要があります。以下は、簡単なパフォーマンステストの例です:

// __tests__/async-performance.test.js
const request = require('supertest');
const express = require('express');
const asyncHandler = require('../utils/async-handler');

describe('非同期ミドルウェアのパフォーマンステスト', () => {
  let app;

  beforeEach(() => {
    app = express();
    
    // 成功する非同期ミドルウェア
    app.get('/performance', asyncHandler(async (req, res) => {
      // 少し遅延を入れる
      await new Promise(resolve => setTimeout(resolve, 10));
      res.json({ success: true });
    }));
    
    // エラーハンドラー
    app.use((err, req, res, next) => {
      res.status(500).json({ error: err.message });
    });
  });

  test('複数の非同期リクエストを処理できる', async () => {
    const startTime = Date.now();
    
    // 10個の並列リクエストを送信
    const requests = Array.from({ length: 10 }).map(() => 
      request(app).get('/performance')
    );
    
    const responses = await Promise.all(requests);
    const endTime = Date.now();
    
    // すべてのリクエストが成功したことを確認
    responses.forEach(response => {
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });
    
    // 実行時間を出力(参考値)
    console.log(`10件の並列リクエスト処理時間: ${endTime - startTime}ms`);
  });
});

テストの実行

以下のコマンドでテストを実行します:

npx jest

テストカバレッジの向上

非同期エラーハンドリングのテストカバレッジを向上させるためのポイント:

  1. エッジケースのテスト: 非同期処理の中での特殊なエラーパターン(例:タイムアウト、ネットワークエラー)に対するテストを追加する

  2. 実際のデータベース接続を使用したテスト: モックではなく実際のデータベースを使用して、非同期操作のエラーハンドリングをテストする(テスト用のデータベースを使用)

  3. ロードテスト: 多数の同時リクエストを処理する際のエラーハンドリングを確認するためのロードテストを実施する

非同期ミドルウェアのエラーハンドリングがテストで検証されていれば、開発過程で問題を早期に発見し、本番環境での予期せぬエラーを防ぐことができます。

本番環境での監視

最後に、本番環境では、未処理のPromise拒否や例外をモニタリングすることも重要です。Node.jsでは以下のようなイベントリスナーを設定できます:

// app.js
process.on('unhandledRejection', (reason, promise) => {
  console.error('未処理のPromise拒否:', reason);
  // エラー監視サービスに通知するなど
});

process.on('uncaughtException', (error) => {
  console.error('未捕捉の例外:', error);
  // エラー監視サービスに通知するなど
});

これらのイベントリスナーをセットアップすることで、非同期ミドルウェアで捕捉されなかったエラーを検出し、適切に対応することができます。

適切なテストと監視を組み合わせることで、Express.jsアプリケーションの信頼性と安定性を大幅に向上させることができます。

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

おすすめ記事

おすすめコンテンツ