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 的 c++ module 链接到 OpenSSL
Aug 03 NodeJs
Nodejs学习笔记之测试驱动
Apr 16 NodeJs
用nodejs的实现原理和搭建服务器(动态)
Aug 10 NodeJs
Nodejs实现短信验证码功能
Feb 09 NodeJs
nodejs入门教程二:创建一个简单应用示例
Apr 24 NodeJs
利用nodeJs anywhere搭建本地服务器环境的方法
May 12 NodeJs
详解NodeJS Https HSM双向认证实现
Mar 12 NodeJs
详解nodejs http请求相关总结
Mar 31 NodeJs
详解利用nodejs对本地json文件进行增删改查
Sep 20 NodeJs
Sublime Text3 配置 NodeJs 环境的方法
May 20 NodeJs
NodeJS配置CORS实现过程详解
Dec 02 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
文件系统基本操作类
2006/11/23 PHP
php array_merge下进行数组合并的代码
2008/07/22 PHP
php 无限级 SelectTree 类
2009/05/19 PHP
基于php iconv函数的使用详解
2013/06/09 PHP
php+croppic.js实现剪切上传图片功能
2018/08/14 PHP
Yii框架应用组件用法实例分析
2020/05/15 PHP
用js实现的一个Flash滚动轮换显示图片代码生成器
2007/03/14 Javascript
jquery tab标签页的制作
2010/05/10 Javascript
JQuery中根据属性或属性值获得元素(6种情况获取方法)
2013/01/17 Javascript
JS弹出层的显示与隐藏示例代码
2013/12/27 Javascript
JS+CSS3制作炫酷的弹窗效果
2016/11/08 Javascript
nodeJS删除文件方法示例
2016/12/25 NodeJs
BackBone及其实例探究_动力节点Java学院整理
2017/07/14 Javascript
详解Nodejs 通过 fs.createWriteStream 保存文件
2017/10/10 NodeJs
three.js实现3D模型展示的示例代码
2017/12/31 Javascript
angularjs实现的购物金额计算工具示例
2018/05/08 Javascript
微信小程序框架wepy之动态控制类名
2018/09/14 Javascript
Vue.set()动态的新增与修改数据,触发视图更新的方法
2018/09/15 Javascript
Vue.js更改调试地址端口号的实例
2018/09/19 Javascript
解决vue项目刷新后,导航菜单高亮显示的位置不对问题
2019/11/01 Javascript
小程序登录之支付宝授权的实现示例
2019/12/13 Javascript
tensorflow实现读取模型中保存的值 tf.train.NewCheckpointReader
2020/02/10 Python
用css3实现当鼠标移进去时当前亮其他变灰效果
2014/04/08 HTML / CSS
CSS3实现王者荣耀匹配人员加载页面的方法
2019/04/16 HTML / CSS
解决margin 外边距合并问题
2019/07/03 HTML / CSS
美国隐形眼镜销售网站:ContactsDirect
2017/10/28 全球购物
阿玛尼美妆英国官网:Giorgio Armani Beauty英国
2019/03/28 全球购物
Java TransactionAPI (JTA) 主要包含几部分
2012/12/07 面试题
优秀毕业大学生推荐信
2013/11/13 职场文书
2014年度安全生产目标管理责任书
2014/07/25 职场文书
领导党的群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
教师自我剖析材料(四风问题)
2014/09/30 职场文书
医院护士党的群众路线教育实践活动对照检查材料思想汇报
2014/10/04 职场文书
党的群众路线剖析材料
2014/10/09 职场文书
java后台调用接口及处理跨域问题的解决
2022/03/24 Java/Android
我家女友可不止可爱呢 公开OP主题曲无字幕动画MV
2022/04/11 日漫