博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
koa compose源码阅读
阅读量:6591 次
发布时间:2019-06-24

本文共 4195 字,大约阅读时间需要 13 分钟。

众所周知,在函数式编程中,compose是将多个函数合并成一个函数(形如: g() + h() => g(h())),koa-compose则是将 koa/koa-router 各个中间件合并执行,结合 next() 就形成了洋葱式模型。

洋葱模型执行顺序

我们创建koa应用如下:

const koa = require('koa');const app = new koa();app.use((ctx, next) => { console.log('第一个中间件函数') await next(); console.log('第一个中间件函数next之后');})app.use(async (ctx, next) => { console.log('第二个中间件函数') await next(); console.log('第二个中间件函数next之后');})app.use(ctx => { console.log('响应'); ctx.body = 'hello'})​app.listen(3000)复制代码

以上代码,可以使用node text-next.js启动,启动后可以在浏览器中访问http://localhost:3000/

访问后,会在启动的命令窗口中打印出如下值:

第一个中间件函数

第二个中间件函数 响应
第二个中间件函数next之后
第一个中间件函数next之后

注意:在使用app.use将给定的中间件添加到应用程序时,中间件(其实就是一个函数)接收两个参数:ctx和next。其中next也是一个函数。

koa-compose源码

再接着深入koa-compose源码之前,我们先来看一下,源代码中是怎么调用compose的。详细参考上一篇。

listen(...args) {   debug('listen');   const server = http.createServer(this.callback());   return server.listen(...args);}​callback() {   // 这里调用的compose的函数,返回值是fn   const fn = compose(this.middleware);​   if (!this.listenerCount('error')) this.on('error', this.onerror);​   const handleRequest = (req, res) => {   const ctx = this.createContext(req, res); // 创建ctx对象   return this.handleRequest(ctx, fn);  // 将fn传递给了this.handleRequest };​ return handleRequest;}​handleRequest(ctx, fnMiddleware) {   const res = ctx.res;   res.statusCode = 404;   onFinished(res, onerror);   // 在这里,看到以下fnMiddleware().then().catch()写法.   // 我们大胆猜测compose函数的返回值是一个function。而且该function的返回值是一个promise对象。   // 待下文源码验证。   return fnMiddleware(ctx)   .then(() => respond(ctx))   .catch(err => ctx.onerror(err));}复制代码

callback函数是在app.listen时执行的,也就是在app.listen时利用 Node 原生的建立http server,并在创建server的时候,处理中间件逻辑。

好,现在我们已经知道了koa是怎么调用compose的,接下来,看koa-compose源代码。 的代码只有不够50行,细读确实是一段很精妙的代码,而实际核心代码则是这一段:

module.exports = compose​function compose (middleware) { // 传入的 middleware 参数必须是数组 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') // middleware 数组的元素必须是函数 for (const fn of middleware) {   if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') }​ // 返回一个函数闭包, 保持对 middleware 的引用。 // 这里也验证了上文的猜测:compose函数的返回值是一个function. // 而且看下文可知,该函数的返回值是promise对象。进一步验证了上文的猜测。 return function (context, next) {   let index = -1   return dispatch(0)   function dispatch (i) {     if (i <= index) return Promise.reject(new Error('next() called multiple times'))     index = i     let fn = middleware[i]     if (i === middleware.length) fn = next     if (!fn) return Promise.resolve()     try {       return Promise.resolve(fn(context, function next () {         return dispatch(i + 1)       }))     } catch (err) {       return Promise.reject(err)     }   }  }}复制代码

虽然短,但是之中使用了4层 return,初看会比较绕,我们只看第3,4层 return,这是返回实际的执行代码链。

return Promise.resolve(fn(context, function next () {   return dispatch(i + 1)}))复制代码

**fn = middleware[i]**也就是某一个中间件,很显然上述代码遍历中间件数组middleware,依次拿到中间件fn,并执行:

fn(context, function next () {   return dispatch(i + 1)})复制代码

这里可以看到传递给中间件的两个参数:context和next函数

前文提到过:在使用app.use将给定的中间件添加到应用程序时,中间件(其实就是一个函数)接收两个参数:ctx和next。其中next也是一个函数。

看到这里是不是明白了,在注册中间件的时候为什么要有两个参数了呐!!!

接下来,我们继续研究洋葱模型到底是怎么回事儿。 比如前文例子中的第一个中间件:

app.use((ctx, next) => { console.log('第一个中间件函数') await next(); console.log('第一个中间件函数next之后');})复制代码
  • 第一次,此时第一个中间件被调用,dispatch(0),展开:
Promise.resolve(((ctx, next) => {   console.log('第一个中间件函数')   await next();   console.log('第一个中间件函数next之后');})(context, function next () {   return dispatch(i + 1)})));复制代码

首先执行console.log('第一个中间件函数'),打出来log没毛病。

接下来注意了老铁!注意了老铁!注意了老铁!重要的事情说三遍。在执行到**await next();**的时候,return dispatch(i + 1)

瞅一眼上文中的dispatch函数,你就能知道,这是递归到了第二个中间件啊,也就是说压根就没执行第二个log即:console.log('第一个中间件函数next之后');,就跑到了第二个中间件。

  • 第二次,此时第二个中间件被调用,dispatch(1),展开:
Promise.resolve((ctx, next) => Promise.resolve((ctx, next) => s{ console.log('第一个中间件函数') await Promise.resolve(((ctx, next) => {   console.log('第二个中间件函数')   await next();   console.log('第二个中间件函数next之后'); })(context, function next () {   return dispatch(i + 1) }))); console.log('第一个中间件函数next之后');});复制代码

接下来的事情,想必你们都猜到了,在第二个中间件执行到await next();时,同样会轮转到第三个中间件,以此类推,直到最后一个中间件。

总结

中间件模型非常好用并且简洁, 甚至在 koa 框架上大放异彩, 但是也有自身的缺陷, 也就是一旦中间件数组过于庞大, 性能会有所下降, 因此我们需要结合自身的情况与业务场景作出最合适的选择.

参考文章:

转载于:https://juejin.im/post/5ce53c956fb9a07ece67a6e5

你可能感兴趣的文章
LVM自动扩容
查看>>
笔记整理4
查看>>
idea文件折叠显示出来配置
查看>>
SQLSERVER中的非工作时间不得插入数据的触发器的实现
查看>>
如何写出兼容大部分浏览器的CSS 代码
查看>>
第二阶段冲刺第八天,6月7日。
查看>>
java的左移位(<<)和右移位(>>)和无符号右移(>>>)
查看>>
struts2 action 返回类型分析
查看>>
【原创】FPGA开发手记(三) PS/2键盘
查看>>
linux统计多个文件大小总和
查看>>
java基础-Eclipse开发工具介绍
查看>>
JS常见的字符串操作
查看>>
洛谷P1069 细胞分裂 数学
查看>>
JAVA中的编码分析
查看>>
查看源代码Source not found及在eclipse中配置jdk的src.zip源代码
查看>>
document.all用法
查看>>
uniGUI试用笔记(二)
查看>>
HOG特征-理解篇
查看>>
Microsoft.AlphaImageLoader滤镜解说
查看>>
extjs_02_grid(显示本地数据,显示跨域数据)
查看>>