Express 错误处理

2018-08-15
nodejs
5286

Node.js中无法使用try/catch处理异步回调函数中的异常。在别的编程语言中,常用try/catch捕获异常。但是在node中,最外层的try/catch, 不能捕获到异步回调函数的异常,可以认为他们不是一个位面的东西。但是try/catch可以捕获一些非异步异常,如

try{
    var result = JSON.parse({status: 'ok'});
} catch (err) {
    console.log(err);
}

对于异步异常,Node提供了两种处理方式:callback回调、'error'事件。而在Express框架中,基于回调传递错误及错误处理中间件,可以捕获系统中所有异步异常并进行统一处理。

next()方法与错误传递

Express是一个Web框架,它对Node的HTTP核心模块中对象进行了扩展,提供了更方便的HTTP请求与响应处理方式。在Express中进行错误处理时,首先要使用next()方法将错误向下传递。 next()app.method()router.method()方法回调函数中的第三个参数。通过next()方法,可以讲请求定向到下一个处理函数或另一个路由中,next()方法可以传递一个状态或错误:

传递一个http状态

对于不存在的某一个或某一类路由,可以使用next()方法传递一个404状态:

router.all('/user/*', function (req, res, next) {
    next(404);
})

当异常出现时,可以使用next()方法传递一个500状态:

router.get('/a/path', function (req, res, next) {
    fs.readFile('a/file', function (err, data) {
        if (err) {
            // 打印一个标准错误,并使用next()方法传递一个500状态
            console.log(err);
            next(500);
        } else {
            res.send('some info');
        }
    });
} )

传递错误信息

请求处理过程中出错时,也可以使用next()方法直接传递这个错误:

router.get('/a/path', funciton (req, res, next) {
    fs.readFile('a/file', function (err, data) {
        if (err) {
            // 使用 next() 方法传递错误信息
            next(err);
        } else {
            res.send('some info');
        }
    })
})

使用promise进行一步处理时,也可以通过next()方法传递出现的异常:

router.get('/a/path', function (req, res, next) {
    promise.then(function (result) {
        res.send('some info');
    }).catch(function (err) {
        next(err);
    });
})

使用中间件处理错误

Express 中的中间件分为两类: 通过router.use()方法挂载的路由中间件和通过app.use()方法挂载的应用中间件,前者只对某一个或一类路由起作用,而后者会对应用范围起作用。

处理异常时,一般会在全局处理。在app.js文件中,Express提供了处理异常的中间件:

// 捕获404并定向到错误处理
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// 错误处理

// 开发环境下的错误处理
// 会输出堆栈信息
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    // 设置响应状态
    res.status(err.status || 500); 
    // 渲染错误处理页
    res.render('error', {
      message: err.message,
      error: err,
      layout: false
    });
  });
}

// 生产环境下的错误处理
// 不会向用户显示堆栈信息
app.use(function(err, req, res, next) {
  // 设置响应状态
  res.status(err.status || 500);
  // 渲染错误处理页
  res.render('error', {
    message: err.message,
    error: {},
    layout: false
  });
});

对于定义的路由,都会由上面第一个中间件捕获,在这里会生成一个404的错误,并将错误向下传递到错误处理路由。

Express提供了两个错误中间件(处理程序),分别用于开发/生产两种模式下的错误处理。在开发模式下,我们一般会输出错误的堆栈信息,以方便调试。而在生产模式下,一般会显示一个错误提示页(如:404-页面未找到、500-服务器内部错误等)。只需要在这两个中间件中渲染不同的页面,就可以实现在不同环境下,对不同错误的统一响应和处理。

全局错误处理

uncaughtException

uncaughtException事件是在程序中有为捕获的错误时触发。默认情况下,node.js在遇到未捕获的错误是,会通过将堆栈跟踪打印到STDRR,并以错误码1退出。uncaughtException 将会覆盖默认行为,并以错误码0退出。

process.on('uncaughtException', (err) => {
    console.error('process uncaughtException:', err)
})

throw new Error('sssss')

// 打印 process uncaughtException: Error: sssss, 然后退出程序

uncaughtException 的设计初衷是让开发者捕获没有记录的错误,然后记录问题。而不是在发现错误之后重启服务。在发现错误最好的方法是解决bug,重新上线。

unhandledRejection

unhandledRejectionuncaughtException 类似。在系统没有捕获到promise的reject状态时,会触发此事件。与uncaughtException不同的在于,unhandledRejection 的错误不会让程序退出。

process.on('unhandledRejection', (reason, promise) => {
    console.error('process unhandledRejection:', reason)
})