1. ホーム
  2. node.js

[解決済み] Node.jsの例外処理のベストプラクティス

2022-03-19 23:49:21

質問内容

数日前からnode.jsを試用し始めたところです。プログラム中に処理されない例外が発生すると、Nodeが終了してしまうことに気づきました。これは、私がこれまで触れてきた通常のサーバーコンテナとは異なるもので、扱われていない例外が発生するとワーカースレッドだけが死に、コンテナはまだリクエストを受け取ることができます。このため、いくつかの疑問が生じます。

  • process.on('uncaughtException') を防ぐ唯一の効果的な方法ですか?
  • ウィル process.on('uncaughtException') は、非同期処理の実行中にも処理されない例外をキャッチするのですか?
  • キャッチできない例外が発生した場合に活用できる、すでに構築されているモジュール(メール送信やファイルへの書き込みなど)はないでしょうか?

node.jsでキャッチできない例外を処理するための一般的なベストプラクティスを示してくれるようなポインタや記事があれば、ありがたいです。

どのように解決するのですか?

アップデート:Joyentは現在 独自のガイド . 以下の情報は、どちらかというと要約です。

エラーを安全に "投げる"する

理想的には、キャッチできないエラーはできるだけ避けたいものです。そのため、文字通りエラーをスローする代わりに、コードアーキテクチャに応じて以下のメソッドのいずれかを使用して安全に "throw" エラーをスローすることができます。

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

    // 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)
    }
    
    
  • コールバックベースの(つまり非同期の)コードでは、コールバックの第1引数は 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
    })
    
    
  • エラーが発生している場所が同期コードで、何らかの理由でドメインが使えない場合(おそらくnodeのバージョンが古い)、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 を使用して非同期関数を記述することができます。

    でもう一つ気をつけなければならないのは 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)望んでいない実行をtry catchがキャッチしてしまう。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