一尘不染

Node.js最佳实践异常处理

node.js

几天前我才开始尝试使用node.js。我意识到只要程序中有未处理的异常,Node就会终止。这与我所见过的普通服务器容器不同,在普通服务器容器中,当发生未处理的异常时,只有工作线程死亡,并且容器仍然能够接收请求。这引起了一些问题:

  • process.on('uncaughtException')唯一有效的预防方法吗?
  • process.on('uncaughtException')在执行异步过程期间也会捕获未处理的异常吗?
  • 是否存在已经构建的模块(例如发送电子邮件或写入文件),在未捕获的异常的情况下可以利用该模块?

我将不胜感激任何向我展示在node.js中处理未捕获异常的常见最佳实践的指针/文章


阅读 250

收藏
2020-07-07

共1个答案

一尘不染

更新: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则为错误,如果未发生错误err则为null。其他任何参数都遵循以下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本机来编写异步函数。

要注意的另一件事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
2020-07-07