错误处理

系统在处理用户请求时,会遇到各种各样的错误情况。如:系统内部错误,url 不存在,没有权限,服务不可用等,这些情况下都需要进行错误输出。

控制器错误处理

在控制器中,错误的输出分为三种情况:

  • API接口错误输出,格式为JSON
  1. this.fail(errmsg, data, code, options);
  2. or
  3. this.error(errmsg, data, code, options);
  4. errmsg 错误提示信息
  5. data 输出数据项
  6. code 错误码,未传入默认500
  7. options:定义错误输出的JSON对象key
  • 给用户显示错误页面
  1. //传入参数为错误模板物理路径
  2. this.assign('errmsg', '没有权限');
  3. this.render(process.env.APP_PATH + '/view/default/error.html');
  • 抛出http错误状态
  1. //抛出404错误
  2. this.ctx.throw(404, '未找到页面');

使用此方式抛出错误,框架会自动拦截,并显示默认的404或500等错误页面。对于用户来讲,并不是很好的体验,建议使用前两种方式做错误处理。

中间件错误处理

中间件中的错误处理直接操作ctx上下文即可:

  1. //使用此方式抛出错误,框架会自动拦截,并显示默认的404或500等错误页面
  2. ctx.throw(500, '发生错误');
  3. return; //直接返回,不再执行下一个中间件

推荐的处理方式

  1. //使用自定义的错误页面
  2. ctx.body = await ctx.compile(templateFile, data);
  3. return;
  4. //或者返回JSON
  5. ctx.body = {"err_code": 500, "err_msg": "发生错误"};
  6. return

服务类或其他文件内处理错误

服务类或其他文件,可以使用Promise.reject()或者 throw Error的方式来抛出错误:

  1. return Promise.reject('错误');
  2. //或者
  3. throw Error('发生错误');

需要注意的是,服务类等功能性的模块,并不会直接提供给http访问,http访问只会经过中间件和控制器。中间件或控制器在调用服务类或其他外部模块的时候,需要对这些外部模块抛出的错误进行拦截和处理,如果没有拦截处理,ThinkKoa框架会自动拦截,并返回统一的http 500 错误页。

PREVENT_NEXT_PROCESS错误

我们在开发的时候,如果使用promise的写法,例如:

  1. let testService = new test();
  2. return testService.getUserInfo().then(info => {
  3. return this.ok('获取用户信息成功', info);
  4. }).catch(e => {
  5. return this.fail('获取用户信息失败' + e.message);
  6. });

往往会出现一个奇怪的错误:

  1. Error: PREVENT_NEXT_PROCESS
  2. ...

这是因为在调用控制器 this.ok、this.fail、this.json、this.jsonp、this.render、this.display等方法时,框架在输出response之后,会自动抛出一个Promise.reject("PREVENT_NEXT_PROCESS")来中断后续代码执行。这个reject异常被上述代码的catch拦截后,就会出现这个错误。

处理方法:

1、使用 async/await:

  1. let testService = new test();
  2. let info = await testService.getUserInfo().catch(e => {
  3. return this.fail('获取用户信息失败' + e.message);
  4. });
  5. return this.ok('获取用户信息成功', info);

2、调换 then和 catch的顺序:

  1. let testService = new test();
  2. return testService.getUserInfo().catch(e => {
  3. return this.fail('获取用户信息失败' + e.message);
  4. }).then(info => {
  5. return this.ok('获取用户信息成功', info);
  6. });