深入解读Node.js中的koa源码(2)

function beforeRequest(request, response, next) { this.requestTime = new Date().valueOf() next() } // 方案1. 修改原handler处理逻辑,进行耗时的统计,然后end发送数据 app.get('/a', beforeRequest, function(request, response) { // 请求耗时的统计 console.log( `${request.url} duration: ${new Date().valueOf() - this.requestTime}` ) response.end('XXX') }) // 方案2. 将输出数据的逻辑挪到一个后置的中间件中 function afterRequest(request, response, next) { // 请求耗时的统计 console.log( `${request.url} duration: ${new Date().valueOf() - this.requestTime}` ) response.end(this.body) } app.get( '/b', beforeRequest, function(request, response, next) { this.body = 'XXX' next() // 记得调用,不然中间件在这里就终止了 }, afterRequest )

无论是哪一种方案,对于原有代码都是一种破坏性的修改,这是不可取的。

因为Express采用了response.end()的方式来向接口请求方返回数据,调用后即会终止后续代码的执行。

而且因为当时没有一个很好的方案去等待某个中间件中的异步函数的执行。

function a(_, _, next) { console.log('before a') let results = next() console.log('after a') } function b(_, _, next) { console.log('before b') setTimeout(_ => { this.body = 123456 next() }, 1000) } function c(_, response) { console.log('before c') response.end(this.body) } app.get('https://www.jb51.net/', a, b, c)

就像上述的示例,实际上log的输出顺序为:

before a before b after a before c

这显然不符合我们的预期,所以在Express中获取next()的返回值是没有意义的。

所以就有了Koa带来的洋葱模型,在Koa1.x出现的时间,正好赶上了Node支持了新的语法,Generator函数及Promise的定义。
所以才有了co这样令人惊叹的库,而当我们的中间件使用了Promise以后,前一个中间件就可以很轻易的在后续代码执行完毕后再处理自己的事情。

但是,Generator本身的作用并不是用来帮助我们更轻松的使用Promise来做异步流程的控制。

所以,随着Node7.6版本的发出,支持了async、await语法,社区也推出了Koa2.x,使用async语法替换之前的co+Generator。

Koa也将co从依赖中移除(2.x版本使用koa-convert将Generator函数转换为promise,在3.x版本中将直接不支持Generator)

由于在功能、使用上Koa的两个版本之间并没有什么区别,最多就是一些语法的调整,所以会直接跳过一些Koa1.x相关的东西,直奔主题。

在Koa中,可以使用如下的方式来定义中间件并使用:

async function log(ctx, next) { let requestTime = new Date().valueOf() await next() console.log(`${ctx.url} duration: ${new Date().valueOf() - requestTime}`) } router.get('https://www.jb51.net/', log, ctx => { // do something... })

因为一些语法糖的存在,遮盖了代码实际运行的过程,所以,我们使用Promise来还原一下上述代码:

function log() { return new Promise((resolve, reject) => { let requestTime = new Date().valueOf() next().then(_ => { console.log(`${ctx.url} duration: ${new Date().valueOf() - requestTime}`) }).then(resolve) }) }

大致代码是这样的,也就是说,调用next会给我们返回一个Promise对象,而Promise何时会resolve就是Koa内部做的处理。
可以简单的实现一下(关于上边实现的App类,仅仅需要修改callback即可):

callback() { return (request, response) => { let { url: path, method } = request let handlers = this.handlers[path] && this.handlers[path][method] if (handlers) { let context = { url: request.url } function next(handlers, index = 0) { return new Promise((resolve, reject) => { if (!handlers[index]) return resolve() handlers[index](context, () => next(handlers, index + 1)).then( resolve, reject ) }) } next(handlers).then(_ => { // 结束请求 response.end(context.body || '404') }) } else { response.end('404') } } }

每次调用中间件时就监听then,并将当前Promise的resolve与reject处理传入Promise的回调中。

也就是说,只有当第二个中间件的resolve被调用时,第一个中间件的then回调才会执行。

这样就实现了一个洋葱模型。

就像我们的log中间件执行的流程:

获取当前的时间戳requestTime

调用next()执行后续的中间件,并监听其回调

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/06ab286a0fe90f6892e979026d11938e.html