以下は、コード例や選択したブログ投稿の引用など、このトピックに関するさまざまなソースからの要約とキュレーションです。ベストプラクティスの完全なリストはここにあります。
Node.JSエラー処理のベストプラクティス
番号1:非同期エラー処理にpromiseを使用する
TL; DR:コールバックスタイルでの非同期エラーの処理は、おそらく地獄への最速の方法(別名:運命のピラミッド)です。コードに与えることができる最高の贈り物は、代わりに、try-catchなどの非常にコンパクトで使い慣れたコード構文を提供する評判の良いpromiseライブラリを使用することです。
それ以外の場合: Node.JSのコールバックスタイル、function(err、response)は、エラー処理とカジュアルコード、過度のネスト、厄介なコーディングパターンが混在しているため、メンテナンスが難しいコードへの有望な方法です。
コード例-良い
doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);
コード例のアンチパターン–コールバックスタイルのエラー処理
getData(someParameter, function(err, result){
if(err != null)
//do something like calling the given callback function and pass the error
getMoreData(a, function(err, result){
if(err != null)
//do something like calling the given callback function and pass the error
getMoreData(b, function(c){
getMoreData(d, function(e){
...
});
});
});
});
});
ブログの引用:「約束に問題があります」
(ブログのpouchdbから、キーワード「Node Promises」で11位にランク付けされました)
「…そして実際、コールバックはさらに不吉なことをします:スタックから私たちを奪います。これは、私たちがプログラミング言語で当たり前のことですが、スタックなしでコードを書くことは、ブレーキペダルなしで車を運転することによく似ています。到達してそれがなくなるまで、どれほどひどくそれが必要であるかを理解しないでください。約束の全体のポイントは、非同期になったときに失った言語の基本、つまりreturn、throw、およびstackを返却することです。しかし、あなたはプロミスを利用するには、プロミスを正しく使用する方法を知っている必要があります。」
数値2:組み込みのエラーオブジェクトのみを使用する
TL; DR:文字列またはカスタムタイプとしてエラーをスローするコードを見るのはよくあることです。これにより、エラー処理ロジックとモジュール間の相互運用性が複雑になります。promiseを拒否するか、例外をスローするか、エラーを発生させるかに関係なく、Node.JS組み込みのErrorオブジェクトを使用すると、統一性が高まり、エラー情報の損失を防ぎます
それ以外の場合:一部のモジュールを実行するときに、どのタイプのエラーが返されるのかが不明である–発生する例外について推論し、それを処理することがはるかに困難になります。価値があるとしても、カスタムタイプを使用してエラーを記述すると、スタックトレースなどの重要なエラー情報が失われる可能性があります。
コード例-正しく行う
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
コード例アンチパターン
//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
throw ("How can I add new product when no value provided?");
ブログの引用:「文字列はエラーではありません」
(ブログdevthoughtから、キーワード「Node.JSエラーオブジェクト」で6位にランク付けされました)
"…エラーの代わりに文字列を渡すと、モジュール間の相互運用性が低下します。これは、instanceof Errorチェックを実行している、またはエラーの詳細を知りたいAPIとのコントラクトを壊します。エラーオブジェクトは、コンストラクターに渡されたメッセージを保持する以外に、最新のJavaScriptエンジンの興味深いプロパティ。」
数値3:運用上のエラーとプログラマーのエラーを区別する
TL; DR:操作エラー(APIが無効な入力を受け取ったなど)は、エラーの影響が完全に理解され、慎重に処理できる既知のケースを指します。一方、プログラマエラー(たとえば、未定義の変数を読み取ろうとした)は、アプリケーションを正常に再起動するように指示する不明なコードエラーを指します。
それ以外の場合:エラーが表示された場合は常にアプリケーションを再起動できますが、軽微で予測されるエラー(操作エラー)のために〜5000人のオンラインユーザーをダウンさせるのはなぜですか?逆も理想的ではありません。不明な問題(プログラマーエラー)が発生したときにアプリケーションを稼働させ続けると、予期しない動作を引き起こす可能性があります。2つを区別することで、巧みに行動し、特定のコンテキストに基づいてバランスのとれたアプローチを適用できます。
コード例-正しく行う
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
コード例-エラーを操作可能(信頼済み)としてマーク
//marking an error object as operational
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;
//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational) {
Error.call(this);
Error.captureStackTrace(this);
this.commonType = commonType;
this.description = description;
this.isOperational = isOperational;
};
throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);
//error handling code within middleware
process.on('uncaughtException', function(error) {
if(!error.isOperational)
process.exit(1);
});
ブログの引用:「それ以外の場合は状態を危険にさらす」(デバッグ可能なブログから、キーワード「Node.JS uncaught exception」でランク3)
" …スローがJavaScriptでどのように機能するかという性質上、参照をリークしたり、他の種類の未定義の脆弱な状態を作成したりせずに、安全に「中断したところから再開する」方法はほとんどありません。最も安全な方法は、スローされたエラーは、プロセスをシャットダウンすることです。もちろん、通常のWebサーバーでは、多数の接続が開いている可能性があります。エラーは他のユーザーによってトリガーされたため、それらを突然シャットダウンすることは合理的ではありません。エラーをトリガーしたリクエストにエラーレスポンスを送信し、他のユーザーは通常の時間に終了し、そのワーカーでの新しいリクエストのリッスンを停止します」
数値4:ミドルウェア内ではなく、ミドルウェア内で集中的にエラーを処理する
TL; DR:管理者へのメールやログなどのエラー処理ロジックは、エラーが発生したときにすべてのエンドポイント(Expressミドルウェア、cronジョブ、ユニットテストなど)が呼び出す専用の集中オブジェクトにカプセル化する必要があります。
それ以外の場合: 1つの場所でエラーを処理しないと、コードが重複し、おそらく不適切に処理されるエラーが発生します。
コード例-一般的なエラーフロー
//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
if (error)
throw new Error("Great error explanation comes here", other useful parameters)
});
//API route code, we catch both sync and async errors and forward to the middleware
try {
customerService.addNew(req.body).then(function (result) {
res.status(200).json(result);
}).catch((error) => {
next(error)
});
}
catch (error) {
next(error);
}
//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
errorHandler.handleError(err).then((isOperationalError) => {
if (!isOperationalError)
next(err);
});
});
ブログの引用:「エラーを発信者に伝える以外に、下位レベルでは何もできない場合があります」(ブログJoyentから、キーワード「Node.JSエラー処理」で1位にランク付けされました)
「…スタックのいくつかのレベルで同じエラーを処理することになるかもしれません。これは、エラーを呼び出し元に伝達すること、呼び出し元にエラーを伝達することなど、より低いレベルでは何も役に立たない場合に発生します。多くの場合、操作の再試行、ユーザーへのエラーの報告など、適切な応答を知っているのはトップレベルの呼び出し元だけです。しかし、それはすべてのエラーを単一のトップレベルに報告することを試みるべきではないという意味ではありません。コールバック、そのコールバック自体はエラーが発生したコンテキストを知ることができないため」
数値5:Swaggerを使用してAPIエラーを文書化する
TL; DR: API呼び出し元に、返される可能性のあるエラーを知らせて、クラッシュせずにこれらを慎重に処理できるようにします。これは通常、SwaggerなどのREST APIドキュメントフレームワークで行われます
それ以外の場合: APIクライアントは、理解できないエラーを受け取ったためにのみクラッシュして再起動することを決定する場合があります。注:APIの呼び出し元はあなたである可能性があります(マイクロサービス環境では非常に一般的)
ブログの引用:「発生する可能性のあるエラーを発信者に伝える必要があります」(ブログJoyentから、キーワード「Node.JS logging」で1位にランク付けされました)
…エラーの処理方法について説明しましたが、新しい関数を作成するとき、関数を呼び出したコードにエラーをどのように配信しますか?…どのようなエラーが発生する可能性があるのかわからない場合、またはそれらの意味がわからない場合は、プログラムは偶然の場合を除いて正しくありません。したがって、新しい関数を作成する場合は、発生する可能性のあるエラーとその意味を発信者に伝える必要があります。
番号6:見知らぬ人が町に来たときにプロセスを優雅にシャットダウンする
TL; DR:不明なエラーが発生した場合(開発者エラー、ベストプラクティス番号#3を参照)-アプリケーションの正常性について不確実性があります。一般的な慣行では、ForeverやPM2のような「リスターター」ツールを使用してプロセスを慎重に再起動することが推奨されています。
それ以外の場合:なじみのない例外がキャッチされると、一部のオブジェクトが不完全な状態になり(たとえば、グローバルに使用され、内部エラーのためにイベントを発生させないイベントエミッター)、将来のすべてのリクエストが失敗するか、異常に動作する可能性があります。
コード例-クラッシュするかどうかの決定
//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
errorManagement.handler.handleError(error);
if(!errorManagement.handler.isTrustedError(error))
process.exit(1)
});
//centralized error handler encapsulates error-handling related logic
function errorHandler(){
this.handleError = function (error) {
return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
}
this.isTrustedError = function(error)
{
return error.isOperational;
}
ブログの引用:「エラー処理に関する考え方は3つあります」(ブログのjsrecipesから)
…エラー処理に関する考え方は主に3つあります。1.アプリケーションをクラッシュさせて再起動します。2.起こり得るすべてのエラーを処理し、クラッシュしないようにします。3. 2つの間のバランスのとれたアプローチ
Number7:成熟したロガーを使用してエラーの可視性を高める
TL; DR: Winston、Bunyan、Log4Jなどの成熟したロギングツールのセットは、エラーの発見と理解をスピードアップします。だからconsole.logを忘れてください。
それ以外の場合: console.logsをスキミングするか、クエリツールやまともなログビューアを使用せずに乱雑なテキストファイルを手動で操作すると、遅くまで仕事で忙しくなる可能性があります。
コード例-Winstonロガーの動作
//your centralized logger object
var logger = new winston.Logger({
level: 'info',
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({ filename: 'somefile.log' })
]
});
//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });
ブログの引用:「(ロガーの)いくつかの要件を特定しましょう:」(ブログstrongblogから)
…いくつかの要件(ロガーの場合)を識別します。1.各ログ行にタイムスタンプを付けます。これはかなり自明です–各ログエントリがいつ発生したかを知ることができるはずです。2.ロギング形式は、人間だけでなくマシンでも簡単に消化できる必要があります。3.複数の構成可能な宛先ストリームを可能にします。たとえば、トレースログを1つのファイルに書き込んでいて、エラーが発生した場合は、同じファイルに書き込んでから、エラーファイルに書き込んで、同時に電子メールを送信するとします。
数値8:APM製品を使用してエラーとダウンタイムを発見する
TL; DR:監視およびパフォーマンス製品(別名APM)は、コードベースまたはAPIをプロアクティブに測定し、欠落していたエラー、クラッシュ、速度の遅い部分を自動的にハイライト表示します
それ以外の場合: APIのパフォーマンスとダウンタイムの測定に多大な労力を費やす可能性がありますが、実際のシナリオで最も遅いコード部分がどれであるか、およびこれらがUXにどのように影響するかについてはおそらく気付かないでしょう。
ブログの引用:「APM製品セグメント」(ブログYoni Goldbergから)
「…APM製品は3つの主要なセグメントを構成します。1。Web サイトまたはAPIの監視 -HTTPリクエストを介して継続的に稼働時間とパフォーマンスを監視する外部サービス。数分でセットアップできます。選択されたいくつかの候補:Pingdom、Uptime Robot、New Relic
2 。コードインストルメンテーション–遅いコード検出、例外統計、パフォーマンスモニタリングなどの機能を活用するためにアプリケーションにエージェントを組み込む必要がある製品ファミリ以下は、選択された少数の候補です:New Relic、App Dynamics
3.オペレーショナルインテリジェンスダッシュボード–これらの製品ラインは、アプリケーションのパフォーマンスを簡単に把握するのに役立つメトリックとキュレーションされたコンテンツを使用してopsチームを促進することに重点を置いています。これには通常、複数の情報ソース(アプリケーションログ、DBログ、サーバーログなど)と事前のダッシュボード設計作業の集約が含まれます。以下は、いくつかの選択された候補です:Datadog、Splunk」
上記は短縮バージョンです- こちらのベストプラクティスと例をご覧ください