NodeJS处理Express中异步错误


Posted in NodeJs onMarch 26, 2017

摘要

比起回调函数,使用 Promise 来处理异步错误要显得优雅许多。

结合 Express 内置的错误处理机制和 Promise 极大地降低产生未捕获错误(uncaught exception)的可能性。

Promise 在ES6中是默认选项。如果使用 Babel 转译,它也可以与 Generators 或者 Async/Await 相结合。

本文主要阐述如何在 Express 中使用错误处理中间件(error-handling middleware)来高效处理异步错误。在 Github 上有对应 代码实例 可供参考。

首先,让我们一起了解 Express 提供的开箱即用的错误处理工具。然后,我们将探讨如何使用 Promise, Generators 以及 ES7 的 async/await 来简化错误处理流程。

Express 内置的异步错误处理

在默认情况下,Express 会捕获所有在路由处理函数中的抛出的异常,然后将它传给下一个错误处理中间件:

app.get('/', function (req, res) {
 throw new Error('oh no!')
})
app.use(function (err, req, res, next) {
 console.log(err.message) // 噢!不!
})

对于同步执行的代码,以上的处理已经足够简单。然而,当异步程序在执行时抛出异常的情况,Express 就无能为力。原因在于当你的程序开始执行回调函数时,它原来的栈信息已经丢失。

app.get('/', function (req, res) {
 queryDb(function (er, data) {
  if (er) throw er
 })
})
app.use(function (err, req, res, next) {
 // 这里拿不到错误信息
})

对于这种情况,可以使用 next 函数来将错误传递给下一个错误处理中间件

app.get('/', function (req, res, next) {
 queryDb(function (err, data) {
  if (err) return next(err)
  // 处理数据

  makeCsv(data, function (err, csv) {
   if (err) return next(err)
   // 处理 csv

  })
 })
})
app.use(function (err, req, res, next) {
 // 处理错误
})

使用这种方法虽然一时爽,却带来了两个问题:

你需要显式地在错误处理中间件中分别处理不同的异常。

一些隐式异常并没有被处理(如尝试获取一个对象并不存在的属性)

利用 Promise 传递异步错误

在异步执行的程序中使用 Promise 处理任何显式或隐式的异常情况,只需要在 Promise 链尾加上 .catch(next) 即可。

app.get('/', function (req, res, next) {
 // do some sync stuff
 queryDb()
  .then(function (data) {
   // 处理数据
   return makeCsv(data)
  })
  .then(function (csv) {
   // 处理 csv
  })
  .catch(next)
})
app.use(function (err, req, res, next) {
 // 处理错误
})

现在,所有异步和同步程序都将被传递到错误处理中间件。棒棒的。

虽然 Promise 让异步错误的传递变得容易,但这样的代码仍然有一些冗长和刻板。这时候 promise generator 就派上了用场。

用 Generators 简化代码

如果你使用的环境原生支持 Generators,你可以手动实现以下的功能。不过这里我们将借用 Bluebird.coroutine 来说明如何使用 Promise generator 来简化刚才的代码。

尽管接下来的例子使用的是 bluebird ,其它 Promise 库(如 co)也都支持 Promise generator.

首先,我们需要使得 Express 路由函数与 Promise generator 兼容:

var Promise = require('bluebird')
function wrap (genFn) { // 1
  var cr = Promise.coroutine(genFn) // 2
  return function (req, res, next) { // 3
    cr(req, res, next).catch(next) // 4
  }
}

这个函数是一个高阶函数,它做了以下几件事情:(分别与代码片段中的注释对应)

以 Genrator 为唯一的输入

让这个函数懂得如何 yield promise

返回一个普通的 Express 路由函数

当这个函数被执行时,它会使用 coroutine 来 yield promise,捕获期间发生的异常,然后将其传递给 next 函数

借助这个函数,我们就可以这样构造路由函数:

app.get('/', wrap(function *(req, res) {
 var data = yield queryDb()
 // 处理数据
 var csv = yield makeCsv(data)
 // 处理 csv
}))
app.use(function (err, req, res, next) {
 // 处理错误
})

现在,Express 的异步错误处理流程的可读性已经近乎令人满意,而且你可以像写同步执行的代码一样去书写异步执行的代码,唯一不要忘了的就是 yield promises。

然而这还不是终点,ES7 的 async/await 提议可以让代码变得更简洁。

使用 ES7 async/await

ES7 async/await 的行为就像 Promise Generator 一样,只不过它可以被用到更多的地方(如类方法或者胖箭头函数)。

为了在 Express 中使用 async/await,同时优雅地处理异步错误,我们仍然需要一个与上文提到的 wrap 类似的函数:

let wrap = fn => (...args) => fn(...args).catch(args[2])
这样,我们就可以按底下这种方式书写路由函数:

app.get('/', wrap(async function (req, res) {
 let data = await queryDb()
 // 处理数据
 let csv = await makeCsv(data)
 // 处理 csv
}))

现在可以愉快地写代码了

有了对同步和异步错误的处理,你可以用新的方式来开发 Express App。但有两点需要注意:

要习惯使用 throw ,它使得你的代码目的明确,throw 会明确地将程序引到错误处理中间件,这对同步或异步的程序都是适用的。
遇到特殊情况,当你觉得有必要时,也可以自行 try/catch。

app.get('/', wrap(async (req, res) => {
   if (!req.params.id) {
    throw new BadRequestError('Missing Id')
   }
   let companyLogo
   try {
    companyLogo = await getBase64Logo(req.params.id)
   } catch (err) {
    console.error(err)
    companyLogo = genericBase64Logo
   }
  }))

要习惯使用 custom error classes ,如 BadRequestError,因为这可以让你在错误处理中间件中更方便地分类处理。

app.use(function (err, req, res, next) {
   if (err instanceof BadRequestError) {
    res.status(400)
    return res.send(err.message)
   }
   ...
  })

需要注意

  1. 以上介绍的方法要求所有异步操作必须返回 promise。如果你的异步操作是使用回调函数的方式,你需要将其转化成 promise。(可以直接使用 Bluebird.promisifyAll 这类函数)
  2. 事件发射器(如 steams)仍然会导致未捕获异常,你需要注意合理地处理这类情况:
NodeJs 相关文章推荐
nodejs开发环境配置与使用
Nov 17 NodeJs
nodejs下打包模块archiver详解
Dec 03 NodeJs
轻松创建nodejs服务器(4):路由
Dec 18 NodeJs
Nodejs学习笔记之NET模块
Jan 13 NodeJs
NodeJS遍历文件生产文件列表功能示例
Jan 22 NodeJs
nodejs搭建本地服务器并访问文件的方法
Mar 03 NodeJs
nodejs中向HTTP响应传送进程的输出
Mar 19 NodeJs
NodeJs form-data格式传输文件的方法
Dec 13 NodeJs
nodejs实现解析xml字符串为对象的方法示例
Mar 14 NodeJs
修改Nodejs内置的npm默认配置路径方法
May 13 NodeJs
nodeJs的安装与npm全局环境变量的配置详解
Jan 06 NodeJs
nodeJS与MySQL实现分页数据以及倒序数据
Jun 05 NodeJs
简单好用的nodejs 爬虫框架分享
Mar 26 #NodeJs
nodejs开发——express路由与中间件
Mar 24 #NodeJs
详解NodeJS框架express的路径映射(路由)功能及控制
Mar 24 #NodeJs
NodeJS学习笔记之Module的简介
Mar 24 #NodeJs
详解nodejs中的process进程
Mar 19 #NodeJs
nodejs中使用HTTP分块响应和定时器示例代码
Mar 19 #NodeJs
nodejs中向HTTP响应传送进程的输出
Mar 19 #NodeJs
You might like
source.php查看源文件
2006/12/09 PHP
Jquery Select操作方法集合脚本之家特别版
2010/05/17 Javascript
brook javascript框架介绍
2011/10/10 Javascript
JavaScript 函数replace深入了解
2013/03/14 Javascript
浅析JavaScript中的同名标识符优先级
2013/12/06 Javascript
jQuery的3种请求方式$.post,$.get,$.getJSON
2014/03/28 Javascript
js点击文本框后才加载验证码实例代码
2015/10/20 Javascript
jquery实现简单的表单验证
2015/11/17 Javascript
vuex实现简易计数器
2016/10/27 Javascript
JavaScript实现经典排序算法之选择排序
2016/12/28 Javascript
Angular客户端请求Rest服务跨域问题的解决方法
2017/09/19 Javascript
webpack4简单入门实例
2018/09/06 Javascript
vue插件draggable实现拖拽移动图片顺序
2018/12/01 Javascript
JS实现的检验身份证格式并输出出生日期,年龄,性别,出生地示例
2019/05/17 Javascript
[46:20]CHAOS vs Alliacne 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
[45:18]完美世界DOTA2联赛循环赛 PXG vs IO 第二场 11.06
2020/11/09 DOTA
python复制与引用用法分析
2015/04/08 Python
python互斥锁、加锁、同步机制、异步通信知识总结
2018/02/11 Python
Python实现的爬虫刷回复功能示例
2018/06/07 Python
python代码过长的换行方法
2018/07/19 Python
Pytorch 实现计算分类器准确率(总分类及子分类)
2020/01/18 Python
css3实现input输入框颜色渐变发光效果代码
2014/04/02 HTML / CSS
联想韩国官网:Lenovo Korea
2018/05/10 全球购物
写好求职信第一句话的技巧
2013/10/26 职场文书
高中毕业生个人自我鉴定
2013/11/24 职场文书
关于抽烟的检讨书
2014/02/25 职场文书
银行行长竞聘演讲稿
2014/04/23 职场文书
旷课检讨书范文
2014/10/30 职场文书
毕业生班级鉴定评语
2015/01/04 职场文书
万能检讨书开头与结尾怎么写
2015/02/17 职场文书
应届生求职自荐信范文
2015/03/04 职场文书
信用卡工资证明范本
2015/06/19 职场文书
发票退票证明
2015/06/24 职场文书
小学远程教育工作总结
2015/08/13 职场文书
《金肉人》米特&《航海王》阿鹤声优松岛实因胰脏癌去世 享寿81岁
2022/04/13 日漫
Redis基本数据类型Zset有序集合常用操作
2022/06/01 Redis