其实这不是一个问题,因为就 koa2 而言,他已经帮我做好了统一错误处理入口 app.onerror
方法。
我们只要覆盖这个方法,就可以统一处理包括 中间件,事件,流 等出现的错误。
但我们始终会看到 UnhandledPromiseRejectionWarning:
类型的错误。
当然,这不一定就是 koa 导致,有可能是其他异步未处理错误导致的,但这都不重要。
让我们来看看 koa 是如何处理全局错误的。
koa2 中间件
官网例子:
1 | const Koa = require('koa'); |
由于 koa2 设计原理,让我们很容易的就实现了一个请求日志中间件。
这里就不上洋葱图了,因为这不是入门教程。
官网上也说了,中间件的 async 可以改写为普通函数。
1 | app.use((ctx, next) => { |
和上面效果一致。
但你知道为什么要加 return
?如果不加 return
会发生什么吗?
多中间件
删除 return
测试后会发现,好像没问题,一切正常。
我们来看个例子:
1 | const Koa = require('koa'); |
打开页面后,如果你看到 hello world
那恭喜你,一切正常。
中间件中的异常
如果我们不小心把 ctx.msg += 'world';
写成了 cxt.msg += 'world';
这种手误相信大家都会遇到吧。
或者干脆直接抛出个错误算了,方便测试。
1 | app.use((ctx, next) => { |
恭喜得到 UnhandledPromiseRejectionWarning: Error: 炸了
错误一枚。
让我们加上 app.onerror
来和谐这个错误吧。
1 | const Koa = require('koa'); |
再次运行,遇到哲学问题了,为什么他没捕获到。
再试试官网中记载的错误处理方法 Error Handling.
1 | const Koa = require('koa'); |
再次运行,,神了,依然也没捕获到,难道官网例子是假的?还是我们下了个假的 koa ?
中间件关联的纽带
其实吧,我们违反了 koa 的设计,有两种方法处理这个问题。
如果不想改成 async 函数,那就在所有 next() 前面加上 return 即可。
如果是 async 函数,那所有 next 前面加 await 即可。
先来看看结果:
1 | const Koa = require('koa'); |
再次运行,可以完美的捕获到错误。
自定义错误处理
如果是自定义异步操作异常呢。
1 | const Koa = require('koa'); |
由于是用户自定义操作,什么时候发生错误其实是未知的。
但我们只要把错误引导到 koa 层面报错,即可利用 app.onerror
统一处理。
1 | app.use(async ctx => { |
这样他的错误其实是在 koa 的控制下 throw 的,可以被 koa 统一捕获到。
中间件原理
说了这么多错误处理方法,还没说为什么要这处理。
当然如果你对原理不感兴趣,其实上面就够了,下面的原理可以忽略。
koa 的中间件其实就是一个平行函数(函数数组)转为嵌套函数的过程。
用到了 koa-compose,除去注释源码就20行左右。
功底扎实的就不需要我多解释了,如果看不懂,那就大致理解为下面这样。
1 | // 我们定义的中间件 |
是不是看的一脸懵逼,那就对了,因为我也不知道怎么表达。
看个类似的问题的,从本质问题出发。
1 | function fn(ctx) { |
执行后输出 { a: 1, b: 1, c: 1, d: 1 }
如果在内层回调中加个错误。
1 | function fn(ctx) { |
跟 koa 中的情况一样,无法捕获,而且抛出 UnhandledPromiseRejectionWarning:
错误。
我们只需要加上 return 即可。
1 | function fn(ctx) { |
这次执行,发现捕获到了。为什么会发生这样的情况呢?
简单说吧,就是 promise 链断掉了。我们只要让他连接起来,不要断掉即可。
所以内层需要 return
否则就相当于 return undefined
导致链断掉了,自然无法被外层 catch 到。
1 | const ctx = { a: 1 }; |
当然改成 async/await 也可以。
中间件设计
官网 issue 中 I can’t catch the error ~ 就有人问了,为什么我捕获不到错误。
回答中说,必须 await 或 return。
但也有人修改了源码,加了个类似 Promise.try
的实现。
然后被人说了,为什么你要违反他本来的设计。
其实没看到这个之前,我也打算自己修改源码的。
很多时候当我们看到代码为什么不那样写的时候,其实人家已经从全局考虑了这个问题。
而我们只是看到了这一个“问题”的解决方法,而没有在更高层面统筹看待问题。