内容简介:众所周知,在函数式编程中,compose是将多个函数合并成一个函数(形如:我们创建koa应用如下:以上代码,可以使用
众所周知,在函数式编程中,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的函数,返回值是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 模块建立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 = -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 框架上大放异彩, 但是也有自身的缺陷, 也就是一旦中间件数组过于庞大, 性能会有所下降, 因此我们需要结合自身的情况与业务场景作出最合适的选择.
参考文章:
koa 源码解析 koa-用到的delegates NPM包详解 redux, koa, express 中间件实现对比解析
以上所述就是小编给大家介绍的《koa compose源码阅读》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【源码阅读】AndPermission源码阅读
- 【源码阅读】Gson源码阅读
- 如何阅读Java源码 ,阅读java的真实体会
- 我的源码阅读之路:redux源码剖析
- JDK源码阅读(六):HashMap源码分析
- 如何阅读源码?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
SCWCD Exam Study Kit Second Edition
Hanumant Deshmukh、Jignesh Malavia、Matthew Scarpino / Manning Publications / 2005-05-20 / USD 49.95
Aimed at helping Java developers, Servlet/JSP developers, and J2EE developers pass the Sun Certified Web Component Developer Exam (SCWCD 310-081), this study guide covers all aspects of the Servlet an......一起来看看 《SCWCD Exam Study Kit Second Edition》 这本书的介绍吧!