1.前言

这节我们主要讲 Koa 常见错误处理方式,总的来讲,Koa 处理异常还是比较方便简单的。

2.简单使用

2.1 ctx.throw(status, message)

Koa 在 content 上下文挂载了一个 throw 对象,专门来处理错误,使用也比较简单。

  1. // controller/user.js
  2. function detail(ctx) {
  3. const id = ctx.params.id;
  4. const {name} = ctx.query;
  5. if(!name) {
  6. ctx.throw(500, 'name is required');
  7. }
  8. ctx.body = {
  9. data: array[id],
  10. success: true
  11. };
  12. }

然后我们访问 http://localhost:3000/user/detail/1, 就会返回下面错误:
image.png
可以看到状态码返回的是 500,但是message 是 Internal Server Error, 并不是我们的 name is required,这是为什么,这是Koa 的一种安全措施,因为 500 代表这后端异常,比如可能是数据库异常,这种错误抛出去是非常不安全的,但是你换成其他 状态码就不会这样,比如换成 422,浏览器显示如下:
image.png
有的人喜欢 直接 throw 异常,比如:

  1. // controller/user.js
  2. function detail(ctx) {
  3. const id = ctx.params.id;
  4. const {name} = ctx.query;
  5. if(!name) {
  6. throw new Error('name is required');
  7. }
  8. ctx.body = {
  9. data: array[id],
  10. success: true
  11. };
  12. }

这这种方式和 和 ctx.throw(500, message) 表现一样,都是出现 Internal Server Error

2.2 中间件统一处理 error

下面是官网例子,注意哈,因为 Koa 中间件是洋葱模型,所以应该把处理错误中间件放在最前面,第一个加载,这样才能捕捉到所有错误。

  1. app.use(async (ctx, next) => {
  2. try {
  3. await next();
  4. } catch (err) {
  5. ctx.status = err.status || 500;
  6. if(ctx.status === 500) {
  7. ctx.body = '服务器异常,请联系管理员';
  8. }
  9. ctx.app.emit('error', err, ctx);
  10. }
  11. });
  12. app.on('error', (err, ctx) => {
  13. // 这里可以做个通知处理,比如发个邮件,钉钉消息啥的
  14. });

Koa官方的错误介绍到这,是不是很简单,一下子就会,太简单了,等等,图样图森破,下面讲讲坑。

3. I can’t catch the error

先来段代码:

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. const router = require('./router')
  4. app.use(async (ctx, next) => {
  5. try {
  6. await next();
  7. } catch (err) {
  8. ctx.status = err.status || 500;
  9. if(ctx.status === 500) {
  10. ctx.body = '服务器异常,请联系管理员';
  11. }
  12. ctx.app.emit('error', err, ctx);
  13. }
  14. });
  15. app.use(async (ctx, next) => {
  16. throw Error('这里泡个错');
  17. ctx.msg += 'world';
  18. next();
  19. });
  20. app.on('error', (err, ctx) => {
  21. console.log('error handle:', err)
  22. // 这里可以做个通知处理,比如发个邮件,钉钉消息啥的
  23. });
  24. app
  25. .use(router.routes())
  26. .use(router.allowedMethods());
  27. app.listen(3000, () => {
  28. console.log(' starting at port 3000')
  29. })

访问下http://localhost:3000/user/detail/1,控制台会打印如下错误
image.png
这里很好的可以看到能看到错误,接下来加几行代码,如下:

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. const router = require('./router')
  4. app.use(async (ctx, next) => {
  5. try {
  6. await next();
  7. } catch (err) {
  8. ctx.status = err.status || 500;
  9. if(ctx.status === 500) {
  10. ctx.body = '服务器异常,请联系管理员';
  11. }
  12. ctx.app.emit('error', err, ctx);
  13. }
  14. });
  15. //新增代码
  16. app.use(async (ctx, next) => {
  17. ctx.msg = 'hello';
  18. next();
  19. });
  20. //新增代码
  21. app.use(async (ctx, next) => {
  22. ctx.msg += ' ';
  23. next();
  24. });
  25. app.use(async (ctx, next) => {
  26. throw Error('这里泡个错');
  27. ctx.msg += 'world';
  28. next();
  29. });
  30. app.on('error', (err, ctx) => {
  31. console.log('error handle:', err)
  32. // 这里可以做个通知处理,比如发个邮件,钉钉消息啥的
  33. });
  34. app
  35. .use(router.routes())
  36. .use(router.allowedMethods());
  37. app.listen(3000, () => {
  38. console.log(' starting at port 3000')
  39. })

访问下http://localhost:3000/user/detail/1,控制台会打印如下错误
image.png
这里出现了 UnhandledPromiseRejectionWarning: Error, 我们通过控制台无法定位到 异常,看不到错误具体行号,
我们把上面代码再改一下
image.png
一个中间件在 next 加上 await,一个加上 return, 这个时候我们再次访问,发现又能看到错误具体行号了,这就是 Koa Github 上 著名 I can’t catch the error

所以中间件 使用过程中记得,next 记得 用 await 或者 return,这样 promise 的链条才不会断掉。

4. 总结

总的来说,Koa 处理 异常还是非常方便的,同时记得在中间件 记得 await next() 或者 return next(),这样才不会出现某些异常捕获不到。最后这节 Demo 地址,可以对着 Demo 走一遍