web-dev-qa-db-ja.com

Expressアプリケーションでの未処理の拒否

エクスプレスアプリ内で実行されるES6約束ベースのコードがたくさんあります。キャッチされないエラーが発生した場合、次のコードを使用して対処します。

process.on('unhandledRejection', function(reason, p) {
  console.log("Unhandled Rejection:", reason.stack);
  process.exit(1);
});

これはデバッグの目的でうまく機能します。

しかし、本番環境では、500エラーハンドラをトリガーして、ユーザーに標準の「問題が発生しました」ページを表示したいと思います。私はこれにより、現在他の例外で機能するすべてのエラーハンドラをキャッチしています:

app.use(function(error, req, res, next) {
  res.status(500);
  res.render('500');
});

ミドルウェア内にunhandledRejectionを配置しても、非同期であり、offenはError: Can't render headers after they are sent to the client.

unhandledRejectionに500ページをレンダリングするにはどうすればよいですか?

51
Daniel

ミドルウェア内にunhandledRejectionを配置すると、多くの場合Error: Can't render headers after they are sent to the client.

エラーハンドラを少し変更します。

// production error handler
const HTTP_SERVER_ERROR = 500;
app.use(function(err, req, res, next) {
  if (res.headersSent) {
    return next(err);
  }

  return res.status(err.status || HTTP_SERVER_ERROR).render('500');
});

ExpressJSドキュメント から:

Expressには、アプリで発生する可能性のあるエラーを処理する組み込みのエラーハンドラが付属しています。このデフォルトのエラー処理ミドルウェアは、ミドルウェアスタックの最後に追加されます。

Next()にエラーを渡し、エラーハンドラーでエラーを処理しない場合、組み込みのエラーハンドラーによって処理されます。エラーはスタックトレースでクライアントに書き込まれます。スタックトレースは運用環境には含まれません。

環境変数NODE_ENVを「プロダクション」に設定して、アプリをプロダクションモードで実行します。

クライアントへの応答のストリーミング中にエラーが発生した場合など、応答の書き込みを開始した後にエラーでnext()を呼び出すと、Expressのデフォルトのエラーハンドラーは接続を閉じ、要求が失敗したと見なします。

そのため、カスタムエラーハンドラを追加する場合、ヘッダーが既にクライアントに送信されている場合、Expressのデフォルトのエラー処理メカニズムに委任する必要があります。

25
Jared Dykstra

next引数をcatchコールバック(別名errback)として使用して、未処理の拒否を転送してエラーハンドラーを表現しています。

app.get('/foo', function (req, res, next) {
  somePromise
    .then(function (result) {
      res.send(result);
    })
    .catch(next); // <----- NOTICE!
}

またはより短い形式:

app.get('/foo', function (req, res, next) {
  somePromise
    .then(function (result) {
       res.send(result); 
    }, next); // <----- NOTICE!
}

そして、エクスプレスエラーハンドラでerr引数を使用して、意味のあるエラー応答を送信できます。

例えば、

app.use(function (err, req, res, /*unused*/ next) {
  // bookshelf.js model not found error
  if (err.name === 'CustomError' && err.message === 'EmptyResponse') {
    return res.status(404).send('Not Found');
  }
  // ... more error cases...
  return res.status(500).send('Unknown Error');
});

私見、グローバルunhandledRejectionイベントは究極の答えではありません。

たとえば、これはメモリリークが発生しやすいです。

app.use(function (req, res, next) {
  process.on('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection:", reason.stack);
    res.status(500).send('Unknown Error');
    //or next(reason);
  });
});

しかし、これは[〜#〜] too [〜#〜] heavy:

app.use(function (req, res, next) {
  var l = process.once('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection:", reason.stack);
    res.status(500).send('Unknown Error');
    //next(reason);
  });
  next();
  process.removeEventLister('unhandledRejection', l);
});

私見、expressjsはPromiseのより良いサポートを必要としています。

20
iolo

express-promise-router は、まさにこの問題を解決するために作られました。ルートがpromiseを返すことができ、そのようなpromiseがエラーで拒否された場合にnext(err)を呼び出します。

11
Thomas

Expressと約束ベースのコードを次のように使用していると仮定します。

readFile() .then(readAnotherFile) .then(doSomethingElse) .then(...)

.catch(next)をpromiseチェーンの最後に追加すると、Expressのミドルウェアは、実動エラーハンドラーを使用して同期/非同期の両方のコードを正常に処理します。

あなたが探しているものに深く掘り下げた素晴らしい記事があります: https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/

3
Rahat Mahbub

デフォルトでは、リクエストがエラーをスローするasync関数の場合、expressはミドルウェアにエラーを渡しません。代わりにprocess.on('unhandledRejection', callback)が呼び出され、リクエストはブロックされます。

ライブラリ express-async-errors は、これらのエラーを解決するために作成されました。

コードにrequire('express-async-errors');を追加する必要があります。ライブラリは、すべての関数がハンドラーに来るようにします。未処理の拒否であっても。

2
David Weinberg