koa compose源码阅读
众所周知,在函数式编程中,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源码之前,我们先来看一下,koa源代码中是怎么调用compose的。详细参考上一篇文章。
listen(...args) {debug('listen');const server = http.createServer(this.callback());return server.listen(...args);
}
callback() {// 这里调用的compose的函数,返回值是fnconst 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 模块建立http server,并在创建server的时候,处理中间件逻辑。
好,现在我们已经知道了koa是怎么调用compose的,接下来,看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 = -1return dispatch(0)function dispatch (i) {if (i <= index) return Promise.reject(new Error('next() called multiple times'))index = ilet fn = middleware[i]if (i === middleware.length) fn = nextif (!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 框架上大放异彩, 但是也有自身的缺陷, 也就是一旦中间件数组过于庞大, 性能会有所下降, 因此我们需要结合自身的情况与业务场景作出最合适的选择.
参考文章:
koa 源码解析 koa-用到的delegates NPM包详解 redux, koa, express 中间件实现对比解析
转载于:https://juejin.im/post/5ce53c956fb9a07ece67a6e5
koa compose源码阅读相关推荐
- koa源码阅读之koa-compose/application.js
koa源码阅读之koa-compose/application.js koa-Compose 为了理解方便特地把注释也粘进来 //这英语.我也来翻译一波 //大概就是把所有的中间件组合返回一个完整大块 ...
- Pytorch TTA(预测增强) 源码阅读
Pytorch TTA 源码阅读 1.ttach/wrappers.py TTA主要调用的接口 继承了pytorch的nn.Module import torch import torch.nn as ...
- 应用监控CAT之cat-client源码阅读(一)
CAT 由大众点评开发的,基于 Java 的实时应用监控平台,包括实时应用监控,业务监控.对于及时发现线上问题非常有用.(不知道大家有没有在用) 应用自然是最初级的,用完之后,还想了解下其背后的原理, ...
- centos下将vim配置为强大的源码阅读器
每日杂事缠身,让自己在不断得烦扰之后终于有了自己的清静时光来熟悉一下我的工具,每次熟悉源码都需要先在windows端改好,拖到linux端,再编译.出现问题,还得重新回到windows端,这个过程太耗 ...
- 源码阅读:AFNetworking(十六)——UIWebView+AFNetworking
该文章阅读的AFNetworking的版本为3.2.0. 这个分类提供了对请求周期进行控制的方法,包括进度监控.成功和失败的回调. 1.接口文件 1.1.属性 /**网络会话管理者对象*/ @prop ...
- 源码阅读:SDWebImage(六)——SDWebImageCoderHelper
该文章阅读的SDWebImage的版本为4.3.3. 这个类提供了四个方法,这四个方法可分为两类,一类是动图处理,一类是图像方向处理. 1.私有函数 先来看一下这个类里的两个函数 /**这个函数是计算 ...
- mybatis源码阅读
说下mybatis执行一个sql语句的流程 执行语句,事务等SqlSession都交给了excutor,excutor又委托给statementHandler SimpleExecutor:每执行一次 ...
- 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment
24 UsageEnvironment使用环境抽象基类--Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类--Live555源码阅读 ...
- Transformers包tokenizer.encode()方法源码阅读笔记
Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode
最新文章
- Pwnium CTF2014 – MatterOfCombination writeup
- python写小程序-你用python写过那些好玩的微信小程序?
- 新兴短距离无线通信技术ZigBee入门到进阶
- 一个整数,它加上100后是一个完全平方数,加上168又是一个完全平方数,请问该数是多少?...
- CheckBoxList 只能选2个选项
- andorid studio 无法识别app项目解决
- 结构专业规范大全_监理签字用语规范大全,就是这么专业!
- 在html中调用js函数
- 语音合成论文优选:Mixture Density Network for Phone-Level Prosody Modelling in Speech Synthesis
- python 空白行_python去掉空白行的多种实现代码
- 如何去掉now函数时间中的汉字
- 转:让老板头疼的90后,管不得?
- POI-HSSFWorkbook合并单元格边框及文字居中问题
- 计算机绘图的实验报告怎么写,计算机绘图—autocad2011实验报告
- 【Lua从青铜到王者基础篇】第十二篇:Lua错误处理
- 关于ARM芯片中的大小端模式
- 土豆网前任CTO开无人便利店,半年达500家
- mac电脑循环次数多少算新_2020年度最佳Mac端App新鲜出炉!今年,你发现了哪些好软件?...
- Windows无法访问指定设备、路径或文件怎么办?
- Qt(C++)项目中使用 Basler 工业相机(图像获取策略)