koa2中间件的错误捕捉与async/await本质

1,895 阅读4分钟

在eggjs项目中官方建议使用中间件来捕获,在前面的文章中介绍过来koa2中间件原理。

try...catch

当时只注意了其执行原理本质上只是递归。但是忽略了try...catch这层代码的作用。

当中间件是同步函数时:

直接运行fn就会被try..catch捕获,从而终止后面的中间件执行。

如果fn成功执行,再利用Promise.resolve来保证返回的是一个promise实例。

当中间件是async函数时:

中间件返回Promise实例,并将其作为参数传入Promise.resolve函数。

Promise.resolve函数有一个特性就是当参数是一个Prmoise实例的时候,会等到此实例被决议才会执行resovle函数,即,等待async函数完全执行完。

这就是koa中间件的核心了:多个异步中间件调用,当前中间件函数体内使用了await next()的写法,会让当前中间件一直等到下个中间件异步执行完毕后再执行自身后续逻辑。

async的实现与错误捕捉

在下方的run函数就是async实现的原理。在run函数中使用try...catch用以捕获错误。

这里是考虑到如果在生成器函数中没有用try...catch捕获错误,那么就在run函数中使用try...catch捕获yield运行时候产生的错误及task.throw(err)的错误并提取结束生成器函数运行。

但是,如果在生成器函数内用try...catch捕获了yield的错误,那么run函数内部的try...catch是不会被触发的。

其实就是相当于嵌套了两层try...catch

生成器函数如何捕获状态为onRejected的Promise实例?

当生成器函数 yield/await 返回一个onRejected状态的promise实例时,run函数中的result.value为promise。

接下来把resulte.value,即,状态为onRejected的Promise实例传入Promise.resolve,Promise.resolve会调用catch内的回调函数。

而catch内的回调函数调用了task.throw,在下一次调用step函数时,如果生成器函数内部没有使用try...catch捕获task.throw,那么就会使用step函数内部的try...catch,并提前终止生成器函数执行。

实例

下面为几个实例

中间件统一处理错误

多个中间件错误捕捉:

中间件1:错误捕捉

中间件2:鉴权

中间件3:其他中间件,抛出错误

输出:

auth before 

test before 

catch error

现象分析

理论上中间件顺序为:error -> auth -> test。由于函数调用栈的原因,会形成一个洋葱圈模型。但是,现在的情况是test执行报错了,因此行为有点不一样。

由实力的输出结果可知:

1. 在第一个中间件中可以捕捉到后面中间件的错误

2. 在每个中间件函数await next()之后的代码并没有执行,即,洋葱圈只剩下一半。

原理分析

1. 在test中间件中抛出了错误,由async的实现原理可知,此中间件返回的是一个onRejected状态的Promise实例,且test函数代码并没有执行完,即,不会执行下一次迭代,那么就终止调用test之后的所有中间件。

2. 再结合koa中间件原理:return Promise.resolve(fn(context, diapatch.bind(null, i+1)))

auth中间件的await next()会等到test执行结束,而test提前终止,并且test返回一个onRejected状态的Promise,导致auth的执行到await next()时也会提前终止,并返回一个新的onRejected状态的Promise

error中间件的await next()也会等到auth执行结束,而同样地,auth提前终止并返回另一个新的onRejected的Promise。

最后再在error中间件函数中采用try...catch捕捉到这个onRejected状态的promise,并对此错误做一个处理。

总结

由async实现原理可知:async既要捕捉同步错误也要捕捉异步错误

1. 在async函数内部可以使用try...catch捕捉一个onRejected状态的Promise实例,可以捕获yield产生的错误(在生成器函数内没有另外捕获的情况下)

2. 在async函数内部不使用try...catch捕捉错误,那么产生错误时候,async函数提前终止,并返回 一个onRejected状态的Promise

由koa中间件原理可知,

中间件函数内await  next()本质上就是递归调用dispatch(i+1)。

1. 如果下一个中间件内部没有使用try...catch捕捉错误,中间件函数就会返回一个onRejected状态的Promise实例,dispatch。提前终止中间件执行。

2. 如果,中间件内部使用了try...catch捕捉错误,则函数会完全执行完。

最后

最后一篇与中间件有关的文章了,,,我觉得我真的懂了.....