class DatabaseError extends ApplicationError { get toString() { return "Errored happend in query: " + this.query + "\n" + this.message; } } // 使用的话 throw new DatabaseError("Other message", { query: query });
化繁为简,集中处理
express
有了基础的错误数据类型后,我们可以在代码里针对不同的错误类型采取不同的解决方案。 接下来,以Express应用为例讲解一下使用方法。
app.use('/user', (req, res, next) => { const data = await database.getData(req.params.userId); if (!data) { throw new NotFoundError("User not found") } // do other thing }); // 错误处理中间件 app.use(async (err, req, res, next) => { if (err instanceof UserFacingError) { res.sendStatus(err.statusCode); // or res.status(err.statusCode).send(err.errorCode) } else { res.sendStatus(500) } // 记录日志 await logger.logError(err, 'parameter:', req.params, 'User Data:', req.user); // 发送邮件 await sendMailToAdminIfCritical(); })
具体到实际场景中,需要在不同的路由中抛出不同的错误类型,然后我们就可以通过在错误处理中间件中进行统一的处理。比如根据不同的错误类型返回不同的错误码。还可以进行记录日志,发送邮件等操作。
database
数据库发生错误的时候,除了常规的抛出错误,有时候你可能还需要进行额外的重试或回退操作,如:
// 发生网络错误的时候隔200ms,重试3次 function query(queryStr, token, repeatTime = 0, delay = 200) { try { await db.query(queryStr); } catch (err) { if (err instanceof NetworkError && repeatTime < 3) { query(queryStr, token, repeatTime + 1, delay); } throw err; } }
未处理错误
对于未处理的错误来说,我们可以使用node.js的unhandledRejection事件进行监听:
process.on('unhandledRejection', error => { console.error('unhandledRejection', error); // To exit with a 'failure' code process.exit(1); });
而且从Node.js 12.0开始,可以使用以下命令启动程序:
node app.js --unhandled-rejections
这样也能够在发现未处理异常的时候进行处理,官方支持了三种对应的处理模式:
strict: Raise the unhandled rejection as an uncaught exception.
warn: Always trigger a warning, no matter if the unhandledRejection hook is set or not but do not print the deprecation warning.
none: Silence all warnings.
总结
最后,总结一下。为了实现可扩展和可维护的错误处理机制,我们可以需要注意以下几个方面:
使用自定义Error类,后续还能根据业务需要进行扩展
将异步代码转换成Promise,然后统一使用async/await + try...catch的形式进行错误捕获
尽量采用统一的错误处理中间件函数
保持Error信息可理解,返回合适的错误状态和代码
对于未处理的错误,要即使捕获并记录