使用场景
异常分类
- 非期望的入参,如函数要求传递的是数值,却传递了字符串
- 意料之外的错误,如:HTTP网络端开,文件不存在等
- 完全意料之外的异常,如业务进程被外部杀死
通用实践
- 需要记录错误的信息,位置,堆栈和上下文
- 根据内容协商来返回不同的响应格式
- 正式环境中,不能将详细的错误信息和堆栈抛到用户侧
Nodejs异常处理
在Nodejs
里,对异常的处理非常重要,如果有未捕获异常
会直接导致进程退出。
在Async Function
异步编程模型出来后,通过try...catch
来捕获错误,就直观的很多。
框架内置支持
egg.js内置了onerror
插件,提供了统一的错误处理机制
对一个请求处理过程中的Middleware
,Controller
,Service
等抛出的任何异常都会被它捕获。
业务错误处理
可以使用middleware
进行统一处理
// app/middleware/error_handler.js
module.exports = () => {
return async function errorHandler(ctx, next) {
try {
await next();
} catch (err) {
const { app } = ctx;
// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
app.emit('error', err, ctx);
const status = err.status || 500;
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
const error = status === 500 && app.config.env === 'prod' ? 'Internal Server Error' : err.message;
// 仅供参考,需按自己的业务逻辑处理。
ctx.body = { error };
ctx.status = status;
}
};
挂载中间件:
module.exports = {
middleware: [ 'errorHandler' ],
errorHandler: {
// 仅对该路径下的接口处理
match: '/api',
},
};
框架兜底处理
框架通过onerror插件提供了统一错误处理机制
对一个请求所有的处理方法(Middleware、Controller、Service)中抛出的任何异常都会被它捕获。

自定义统一异常处理
尽管框架提供了默认统一异常处理机制,但是应用开发经常需要对异常的响应做自定义,特别是在做一些接口开发的时候。框架自带的 onerror 插件支持自定义配置错误处理方法,可以覆盖默认的错误处理方法。
// config/config.default.js
module.exports = {
onerror: {
all(err, ctx) {
// 在此处定义针对所有响应类型的错误处理方法
// 注意,定义了 config.all 之后,其他错误处理方法不会再生效
ctx.body = 'error';
ctx.status = 500;
},
html(err, ctx) {
// html hander
ctx.body = '<h3>error</h3>';
ctx.status = 500;
},
json(err, ctx) {
// json hander
ctx.body = { message: 'error' };
ctx.status = 500;
},
jsonp(err, ctx) {
// 一般来说,不需要特殊针对 jsonp 进行错误定义,jsonp 的错误处理会自动调用 json 错误处理,并包装成 jsonp 的响应格式
},
},
};
自定义404响应
在一些场景下,我们需要自定义服务器 404 时的响应,只需要加入一个中间件即可统一处理:
// app/middleware/notfound_handler.js
module.exports = () => {
return async function notFoundHandler(ctx, next) {
await next();
if (ctx.status === 404 && !ctx.body) {
if (ctx.acceptJSON) {
ctx.body = { error: 'Not Found' };
} else {
ctx.body = '<h1>Page Not Found</h1>';
}
}
};
};
常见问题
该不该 Catch
如果错误是非主流程的,是可选的,可以使用try...catch
自行兜底处理。
回调错误无法捕获
正常的写法,所有的异常都可以捕获并处理,但是有些特殊的写法可能带来问题。
所有的异步操作都通过await
串联起来,但是只要有一个地方跳出来异步调用链,异常就捕获不到了。
// app/controller/home.js
class HomeController extends Controller {
async buy () {
const { ctx } = this;
const config = await ctx.service.trade.buy({ id: '12345' });
// 下单后需要进行一次核对,且不阻塞当前请求
setImmediate(() => {
ctx.service.trade.check(request).catch(err => ctx.logger.error(err));
});
}
}
但是由于 setImmediate 中的代码『跳出』了异步链,它里面的错误就无法被捕捉到了。
框架提供了ctx. runInBackground (scope)
辅助方法,通过它包装了一个异步链,所有在这个scope里面的错误都会统一进行捕获。
class HomeController extends Controller {
async buy () {
const request = {};
const config = await ctx.service.trade.buy(request);
// 下单后需要进行一次核对,且不阻塞当前请求
ctx.runInBackground(async () => {
// 这里面的异常都会统统被 Backgroud 捕获掉,并打印错误日志
await ctx.service.trade.check(request);
});
}
}
网友评论