Node.jsのベストプラクティスの例外処理


755

数日前にnode.jsを試してみたところです。プログラムに未処理の例外があると、ノードが終了することに気づきました。これは、未処理の例外が発生したときにワーカースレッドのみが終了し、コンテナーがまだ要求を受信できる場合に遭遇した通常のサーバーコンテナーとは異なります。これはいくつかの質問を引き起こします:

  • process.on('uncaughtException')それを防ぐ唯一の効果的な方法はありますか?
  • ウィルprocess.on('uncaughtException')だけでなく、非同期プロセスの実行中に未処理の例外をキャッチ?
  • キャッチされていない例外の場合に活用できる、すでに構築されているモジュール(電子メールの送信やファイルへの書き込みなど)はありますか?

node.jsでキャッチされなかった例外を処理するための一般的なベストプラクティスを教えてくれるポインタ/記事に感謝します


11
キャッチされない例外は発生しません。アプリケーションがクラッシュしたときにアプリケーション全体を再起動するプログラムを使用する場合(nodemon、forever、supervisor)
Raynos

116
非同期コードのすべての部分をに入れない限り、キャッチされない例外が常に発生する可能性があります。try .. catchまた、これがすべてのライブラリ
Dan

13
+1ダン最初は、すべての「スレッドエントリポイント」をtry / catchesのコードにラップするだけでよいので、すべてのライブラリは少し大げさだと思いました。しかし、もっと慎重にそれについて考え、どのlibに持つことができるsetTimeoutか、setIntervalあなたのコードでキャッチすることができないどこかに深く埋もれている種類のか何かを。
Eugene Beresovsky

8
@EugeneBeresovksy Danは正しいですが、uncaughtExceptionsが発生したときにアプリを再起動するしか安全でないという事実は変わりません。つまり、アプリがクラッシュし、それに対して実行できることや実行する必要があることは何もありません。何か建設的なことをしたい場合は、新しい実験的なv0.8ドメイン機能を実装して、クラッシュをログに記録し、5xx応答をクライアントに送信できるようにします。
ostergaard、2013年

1
@Danすべてのコールバック関数をtry .. catchで囲んでも、エラーのキャッチは保証されません。必要なモジュールが独自のバイナリを使用している場合、それらは異常にクラッシュする可能性があります。私はこれをphantomjs-nodeで発生させ、キャッチできないエラーで失敗しました(必要なバイナリで何らかのプロセス検査を行う必要があったが、それを追求したことがない場合を除きます)。
Trindaz 2013年

回答:


737

更新:Joyentには独自のガイドがあります。以下の情報は、要約の詳細です。

安全にエラーを「投げる」

理想的には、キャッチされていないエラーをできるだけ避けたいので、文字通りエラーをスローする代わりに、コードアーキテクチャに応じて次のいずれかの方法を使用してエラーを安全に「スロー」できます。

  • 同期コードの場合、エラーが発生した場合はエラーを返します。

    // Define divider as a syncrhonous function
    var divideSync = function(x,y) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by returning it
            return new Error("Can't divide by zero")
        }
        else {
            // no error occured, continue on
            return x/y
        }
    }
    
    // Divide 4/2
    var result = divideSync(4,2)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/2=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/2='+result)
    }
    
    // Divide 4/0
    result = divideSync(4,0)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/0=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/0='+result)
    }
  • コールバックベースの(つまり非同期の)コードのerr場合、コールバックの最初の引数はです。errエラーが発生した場合はエラー、エラーが発生しなかった場合errnullです。他の引数は引数の後に続きerrます。

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    divide(4,2,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/2=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/2='+result)
        }
    })
    
    divide(4,0,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/0=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/0='+result)
        }
    })
  • 以下のための波乱の誤差は、どこでも発生する代わりに、エラーを投げる、火もコード、error代わりにイベントを

    // Definite our Divider Event Emitter
    var events = require('events')
    var Divider = function(){
        events.EventEmitter.call(this)
    }
    require('util').inherits(Divider, events.EventEmitter)
    
    // Add the divide function
    Divider.prototype.divide = function(x,y){
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by emitting it
            var err = new Error("Can't divide by zero")
            this.emit('error', err)
        }
        else {
            // no error occured, continue on
            this.emit('divided', x, y, x/y)
        }
    
        // Chain
        return this;
    }
    
    // Create our divider and listen for errors
    var divider = new Divider()
    divider.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    divider.on('divided', function(x,y,result){
        console.log(x+'/'+y+'='+result)
    })
    
    // Divide
    divider.divide(4,2).divide(4,0)

安全にエラーを「キャッチ」

ただし、場合によっては、どこかにエラーをスローするコードがまだ存在する可能性があり、キャッチされない例外と、安全にキャッチしないとアプリケーションがクラッシュする可能性があります。コードアーキテクチャに応じて、次のいずれかの方法でキャッチできます。

  • エラーが発生している場所がわかったら、そのセクションをnode.jsドメインにラップできます。

    var d = require('domain').create()
    d.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    
    // catch the uncaught errors in this asynchronous or synchronous code block
    d.run(function(){
        // the asynchronous or synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    })
  • エラーが発生している場所が同期コードであることがわかっていて、何らかの理由でドメイン(おそらく古いバージョンのノード)を使用できない場合は、try catchステートメントを使用できます。

    // catch the uncaught errors in this synchronous code block
    // try catch statements only work on synchronous code
    try {
        // the synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    } catch (err) {
        // handle the error safely
        console.log(err)
    }

    ただし、try...catch非同期でスローされたエラーはキャッチされないため、非同期コードでは使用しないように注意してください。

    try {
        setTimeout(function(){
            var err = new Error('example')
            throw err
        }, 1000)
    }
    catch (err) {
        // Example error won't be caught here... crashing our app
        // hence the need for domains
    }

    try..catch非同期コードと連携して作業したい場合は、Node 7.4以降を実行しているときにasync/awaitネイティブで非同期関数を作成できます。

    もう1つの注意点try...catchは、次のtryようにステートメント内で完了コールバックをラップするリスクです。

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    var continueElsewhere = function(err, result){
            throw new Error('elsewhere has failed')
    }
    
    try {
            divide(4, 2, continueElsewhere)
            // ^ the execution of divide, and the execution of 
            //   continueElsewhere will be inside the try statement
    }
    catch (err) {
            console.log(err.stack)
            // ^ will output the "unexpected" result of: elsewhere has failed
    }

    コードがより複雑になるため、これは非常に簡単です。したがって、ドメインを使用するか、エラーを返すことで、(1)非同期コードでキャッチされない例外を回避する(2)不要な実行をキャッチしようとする試行を回避するのが最善です。JavaScriptの非同期イベントマシンスタイルではなく、適切なスレッド化を可能にする言語では、これはそれほど問題ではありません。

  • 最後に、ドメインまたはtry catchステートメントでラップされていない場所でキャッチされないエラーが発生した場合、uncaughtExceptionリスナーを使用してアプリケーションをクラッシュさせないようにすることができます(ただし、アプリケーションを不明な状態にする可能性があります)):

    // catch the uncaught errors that weren't wrapped in a domain or try catch statement
    // do not use this in modules, but only in applications, as otherwise we could have multiple of these bound
    process.on('uncaughtException', function(err) {
        // handle the error safely
        console.log(err)
    })
    
    // the asynchronous or synchronous code that emits the otherwise uncaught error
    var err = new Error('example')
    throw err

5
ありがとう、レイノス、更新。の悪を説明する情報源はありますtry catchか?それを証拠でバックアップしたいので。同期の例も修正しました。
バラプトン2011

2
この回答は無効になりました。Domainsがこの問題を解決します(node.jsで推奨)
Gabriel Llamas

5
@baluptonエラー処理のためにエラーをスローする必要があります。絶対に避けてはいけません。それらについて、アプリの実行やその他の何かを壊すものは何もありません。Javaと他のほとんどの最近の言語は、例外に対する優れたサポートを備えています。ここで誤った形式の投稿をいくつか読んだ後の私の唯一の結論は、人々がそれらをあまりよく理解していないため、それらを恐れているということです。不確かな疑いを恐れます。この議論は少なくとも20年前に例外を支持して決定的に決定されました。
enl8enmentnow 2015

22
現在、ドメインはio.jsによって非推奨になっています。「このモジュールは非推奨の保留中です。代替APIが確定すると、このモジュールは完全に非推奨になります...ドメインが提供する機能を絶対に必要とするユーザーは、当面はそれに依存する可能性がありますが、将来、別のソリューションに移行する必要があることを期待する必要があります。」
Timothy Gu

5
ドメインAPIが廃止されますか?彼らは代わりのAPIについて言及しています-これがいつ出るのか誰が知っているのですか?
UpTheCreek

95

以下は、コード例や選択したブログ投稿の引用など、このトピックに関するさまざまなソースからの要約とキュレーションです。ベストプラクティスの完全なリストはここにあります。


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」


上記は短縮バージョンです- こちらのベストプラクティスと例をご覧ください


30

キャッチされない例外をキャッチすることはできますが、用途は限られています。http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cbを参照してください

monitforeverまたは、upstartクラッシュしたときにノードプロセスを再起動するために使用できます。正常なシャットダウンが期待できます(たとえば、すべてのメモリ内データをキャッチされない例外ハンドラーに保存します)。


4
+1リンクは役に立ちます。ありがとうございます。node.jsのコンテキストでのベストプラクティスと「グレースフルリスタート」の意味をまだ探しています
momo

この文脈での「グレースフルリスタート」についての私の理解は、本質的にnponeccopが示唆することです。つまり、プロセスを終了させ、最初に実行しているものに再起動させます。
Ilkka

そのリンクをどうもありがとう!本当に便利です!
SatheeshJM 2012

これは素晴らしい答えです。ただし、最初の例ではエラーを返すことについては同意しません。an Errorを返すと、戻り値がポリモーフィックになり、関数のセマンティクスが不必要に混乱します。さらに、0によるダイビングは、JavaScriptで、、またはの値を指定することInfinityですでに処理されています。で確認できます。一般に、関数からエラーを返さないことをお勧めします。コードの読みやすさとメンテナンスの点で、一貫したセマンティクスで特別な非ポリモーフィック値をスローまたは返す方が優れています。-InfinityNaNtypeof === 'number'!isFinite(value)
wprl 2015

リンクが壊れています。 downforeveryoneorjustme.com/debuggable.com
Kev

13

nodejsドメインは、nodejsのエラーを処理する最新の方法です。ドメインは、エラーやその他のイベントだけでなく、従来からスローされていたオブジェクトもキャプチャできます。ドメインは、interceptメソッドを介して最初の引数としてエラーが渡されたコールバックを処理する機能も提供します。

通常のtry / catchスタイルのエラー処理と同様に、通常、エラーが発生したときにスローし、エラーを分離して残りのコードに影響を与えたくない領域をブロックするのが最善です。これらの領域を「ブロックアウト」する方法は、分離されたコードのブロックとして関数を使用してdomain.runを呼び出すことです。

同期コードでは、上記で十分です。エラーが発生した場合、エラーをスローするか、エラーをキャッチして処理し、元に戻す必要があるデータを元に戻します。

try {  
  //something
} catch(e) {
  // handle data reversion
  // probably log too
}

エラーが非同期コールバックで発生した場合、データ(共有状態、データベースなどの外部データ)のロールバックを完全に処理できる必要があります。または、例外が発生したことを示すために何かを設定する必要があります。そのフラグを気にする場合は、コールバックが完了するまで待つ必要があります。

var err = null;
var d = require('domain').create();
d.on('error', function(e) {
  err = e;
  // any additional error handling
}
d.run(function() { Fiber(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(err != null) {
    // handle data reversion
    // probably log too
  }

})});

上記のコードの一部は見苦しいものですが、次のように、自分でパターンを作成して見栄えを良くすることができます。

var specialDomain = specialDomain(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(specialDomain.error()) {
    // handle data reversion
    // probably log too
  } 
}, function() { // "catch"
  // any additional error handling
});

更新(2013-09):

上記では、ファイバーのセマンティクスを意味するフューチャーを使用しています。これにより、インラインでフューチャーを待つことができます。これにより、実際にすべてに対して従来のtry-catchブロックを使用できます。これが最善の方法であることがわかりました。ただし、これを常に実行できるわけではありません(つまり、ブラウザで)...

ファイバーのセマンティクスを必要としないフューチャーもあります(通常のブラウザーJavaScriptで動作します)。これらは、先物、約束、または据え置きと呼ぶことができます(ここから先物についてのみ言及します)。昔ながらのJavaScript先物ライブラリを使用すると、先物間でエラーを伝播できます。これらのライブラリの一部だけが、スローされたフューチャーを正しく処理できるようにしているので、注意してください。

例:

returnsAFuture().then(function() {
  console.log('1')
  return doSomething() // also returns a future

}).then(function() {
  console.log('2')
  throw Error("oops an error was thrown")

}).then(function() {
  console.log('3')

}).catch(function(exception) {
  console.log('handler')
  // handle the exception
}).done()

これは、部分が非同期であっても、通常のtry-catchを模倣しています。それは印刷します:

1
2
handler

そのフローを中断する例外がスローされたため、「3」は出力されないことに注意してください。

ブルーバードの約束を見てみましょう:

スローされた例外を適切に処理するライブラリー以外に、他の多くのライブラリーを見つけていないことに注意してください。たとえば、jQueryの据え置きはしないでください。「失敗」ハンドラーは、例外として、「then」ハンドラーをスローしません。これは、私の意見では取引ブレーカーです。


Javascriptの適切なpromise仕様は、Promises / A +として知られています。実装のリストは、github.com / promises-aplus / promises-spec / blob / master / …で確認できます。裸のPromises / A +は実際には使用できないことに注意してください-Promises / A +は、ライブラリが解決する多くの実用的な問題を残しています。ただし、表示するエラーの伝播、確定的な実行順序、スタックオーバーフローからの安全性など、絶対に不可欠なものが保証されます。
エサイリヤ2013年


11

私は最近これについてhttp://snmaynard.com/2012/12/21/node-error-handling/で書きました。バージョン0.8のノードの新機能はドメインであり、エラー処理のすべての形式を1つの簡単な管理形式に組み合わせることができます。あなたは私の記事でそれらについて読むことができます。

Bugsnagのようなものを使用して、キャッチされていない例外を追跡し、メール、チャットルームで通知を受けたり、キャッチされていない例外のチケットを作成したりすることもできます(私はBugsnagの共同創設者です)。


2
ドメインモジュールは正式に非推奨になりました。nodejs.org/api/domain.html
MattSidor

3

Step.jsライブラリが例外を処理するのに役立ち、常に次のステップ関数に渡すことを追加したいと思います。したがって、最後のステップとして、前のステップのいずれかでエラーをチェックする機能を持つことができます。このアプローチにより、エラー処理を大幅に簡略化できます。

以下はgithubページからの引用です:

スローされた例外はすべてキャッチされ、最初の引数として次の関数に渡されます。コールバック関数をメイン関数にインライン化しない限り、これにより、キャッチされない例外が発生するのを防ぐことができます。単一のキャッチされない例外がサーバー全体をダウンさせる可能性があるため、これは長時間実行されているnode.JSサーバーにとって非常に重要です。

さらに、Stepを使用してスクリプトの実行を制御し、最後のステップとしてセクションをクリーンアップできます。たとえば、Nodeでビルドスクリプトを記述して、書き込みにかかった時間を報告したい場合、最後のステップでそれを実行できます(最後のコールバックを掘り下げるのではなく)。


3

try-catchの使用が適切である1つの例は、forEachループを使用する場合です。これは同期ですが、同時に内部スコープでreturnステートメントを使用することはできません。代わりに、try and catchアプローチを使用して、適切なスコープでErrorオブジェクトを返すことができます。検討してください:

function processArray() {
    try { 
       [1, 2, 3].forEach(function() { throw new Error('exception'); }); 
    } catch (e) { 
       return e; 
    }
}

これは、上記の@baluptonで説明されているアプローチの組み合わせです。


エラーをスローする代わりに、失敗が既知の可能性がある場合に、RustのResultコンセプトを使用してOKまたはFailを返すことを推奨する開発者もいます。これにより、予期しないエラーから障害を分離できます。これのJS実装の1つはr-resultです。
joeytwiddle

これはアプリ全体の設計決定です。エラーを返すというあなたの概念はほぼ同等であり、開始するのは簡単ですが(追加の依存関係はありません)、明示的ではありません(結果により、障害を処理する必要がある場合を痛感することになります)。不必要に建てられました。
joeytwiddle

1

しばらく前にこの投稿を読んだ後、api /関数レベルでの例外処理にドメインを使用するのが安全かどうか疑問に思いました。それらを使用して、私が書いた各非同期関数の例外処理コードを単純化したかったのです。私の懸念は、各機能に新しいドメインを使用すると、かなりのオーバーヘッドが発生することでした。私の宿題は、オーバーヘッドが最小限であり、一部の状況ではトライキャッチよりドメインの方が実際にはパフォーマンスが優れていることを示しているようです。

http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/


1

エラーのキャッチについては、ここで非常によく説明されていますが、エラーをどこかにログアウトして、エラーを表示して修正できるようにしておくことを忘れないでください。

BunyanはNodeJSの人気のあるロギングフレームワークです。console.logを避けている限り、さまざまな出力場所に書き出すので、ローカルデバッグに役立ちます。ドメインのエラーハンドラで、エラーをログファイルに出力できます。

var log = bunyan.createLogger({
  name: 'myapp',
  streams: [
    {
      level: 'error',
      path: '/var/tmp/myapp-error.log'  // log ERROR to this file
    }
  ]
});

チェックするエラーやサーバーが多い場合、これは時間がかかる可能性があります。そのため、Raygun(免責事項、私はRaygunで働いています)などのツールを調べて、エラーをグループ化するか、両方を一緒に使用することをお勧めします。Raygunをツールとして使用することに決めた場合、セットアップも非常に簡単です

var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);

PM2などのツールを使用したり、永久に使用したりしても、アプリはクラッシュし、何が起こったかをログアウトして、大きな問題なく再起動できるはずです。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.