1. 前言
大家好,我是若川。欢送关注我的公众号若川视线,最近组织了源码共读流动《1个月,200+人,一起读了4周源码》,感兴趣的能够加我微信 ruochuan12 参加,长期交流学习。
之前写的《学习源码整体架构系列》 蕴含jQuery
、underscore
、lodash
、vuex
、sentry
、axios
、redux
、koa
、vue-devtools
、vuex4
十余篇源码文章。其中最新的两篇是:
Vue 3.2 公布了,那尤雨溪是怎么公布 Vue.js 的?
初学者也能看懂的 Vue3 源码中那些实用的根底工具函数
写绝对很难的源码,消耗了本人的工夫和精力,也没播种多少浏览点赞,其实是一件挺受打击的事件。从浏览量和读者受害方面来看,不能促成作者继续输入文章。
所以转变思路,写一些绝对通俗易懂的文章。其实源码也不是设想的那么难,至多有很多看得懂。
之前写过 koa 源码文章学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理比拟长,读者敌人大概率看不完,所以本文从koa-compose
50行源码讲述。
本文波及到的 koa-compose 仓库 文件,整个index.js
文件代码行数尽管不到 50
行,而且测试用例test/test.js
文件 300
余行,但十分值得咱们学习。
歌德曾说:读一本好书,就是在和崇高的人谈话。 同理可得:读源码,也算是和作者的一种学习交换的形式。
浏览本文,你将学到:
1. 相熟 koa-compose 中间件源码、能够应答面试官相干问题2. 学会应用测试用例调试源码3. 学会 jest 局部用法
2. 环境筹备
2.1 克隆 koa-compose 我的项目
本文仓库地址 koa-compose-analysis,求个star
~
# 能够间接克隆我的仓库,我的仓库保留的 compose 仓库的 git 记录git clone https://github.com/lxchuan12/koa-compose-analysis.gitcd koa-compose/composenpm i
顺带说下:我是怎么保留 compose
仓库的 git
记录的。
# 在 github 上新建一个仓库 `koa-compose-analysis` 克隆下来git clone https://github.com/lxchuan12/koa-compose-analysis.gitcd koa-compose-analysisgit subtree add --prefix=compose https://github.com/koajs/compose.git main# 这样就把 compose 文件夹克隆到本人的 git 仓库了。且保留的 git 记录
对于更多 git subtree
,能够看这篇文章用 Git Subtree 在多个 Git 我的项目间双向同步子项目,附扼要使用手册
接着咱们来看怎么依据开源我的项目中提供的测试用例调试源码。
2.2 依据测试用例调试 compose 源码
用VSCode
(我的版本是 1.60
)关上我的项目,找到 compose/package.json
,找到 scripts
和 test
命令。
// compose/package.json{ "name": "koa-compose", // debug (调试) "scripts": { "eslint": "standard --fix .", "test": "jest" },}
在scripts
上方应该会有debug
或者调试
字样。点击debug
(调试),抉择 test
。
接着会执行测试用例test/test.js
文件。终端输入如下图所示。
接着咱们调试 compose/test/test.js
文件。
咱们能够在 45行
打上断点,从新点击 package.json
=> srcipts
=> test
进入调试模式。
如下图所示。
接着按上方的按钮,持续调试。在compose/index.js
文件中要害的中央打上断点,调试学习源码事倍功半。
更多 nodejs 调试相干 能够查看官网文档
顺便提一下几个调试相干按钮。
- 持续(F5)
- 单步跳过(F10)
- 单步调试(F11)
- 单步跳出(Shift + F11)
- 重启(Ctrl + Shift + F5)
- 断开链接(Shift + F5)
接下来,咱们跟着测试用例学源码。
3. 跟着测试用例学源码
分享一个测试用例小技巧:咱们能够在测试用例处加上only
润饰。
// 例如it.only('should work', async () => {})
这样咱们就能够只执行以后的测试用例,不关怀其余的,不会烦扰调试。
3.1 失常流程
关上 compose/test/test.js
文件,看第一个测试用例。
// compose/test/test.js'use strict'/* eslint-env jest */const compose = require('..')const assert = require('assert')function wait (ms) { return new Promise((resolve) => setTimeout(resolve, ms || 1))}// 分组describe('Koa Compose', function () { it.only('should work', async () => { const arr = [] const stack = [] stack.push(async (context, next) => { arr.push(1) await wait(1) await next() await wait(1) arr.push(6) }) stack.push(async (context, next) => { arr.push(2) await wait(1) await next() await wait(1) arr.push(5) }) stack.push(async (context, next) => { arr.push(3) await wait(1) await next() await wait(1) arr.push(4) }) await compose(stack)({}) // 最初输入数组是 [1,2,3,4,5,6] expect(arr).toEqual(expect.arrayContaining([1, 2, 3, 4, 5, 6])) })}
大略看完这段测试用例,context
是什么,next
又是什么。
在koa
的文档上有个十分代表性的中间件 gif
图。
而compose
函数作用就是把增加进中间件数组的函数依照下面 gif
图的程序执行。
3.1.1 compose 函数
简略来说,compose
函数次要做了两件事件。
- 接管一个参数,校验参数是数组,且校验数组中的每一项是函数。
- 返回一个函数,这个函数接管两个参数,别离是
context
和next
,这个函数最初返回Promise
。
- 返回一个函数,这个函数接管两个参数,别离是
/** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */function compose (middleware) { // 校验传入的参数是数组,校验数组中每一项是函数 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch(i){ // 省略,下文讲述 } }}
接着咱们来看 dispatch
函数。
3.1.2 dispatch 函数
function dispatch (i) { // 一个函数中屡次调用报错 // await next() // await next() if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i // 取出数组里的 fn1, fn2, fn3... let fn = middleware[i] // 最初 相等,next 为 undefined if (i === middleware.length) fn = next // 间接返回 Promise.resolve() if (!fn) return Promise.resolve() try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1))) } catch (err) { return Promise.reject(err) }}
值得一提的是:bind
函数是返回一个新的函数。第一个参数是函数里的this指向(如果函数不须要应用this
,个别会写成null
)。
这句fn(context, dispatch.bind(null, i + 1)
,i + 1
是为了 let fn = middleware[i]
取middleware
中的下一个函数。
也就是 next
是下一个中间件里的函数。也就能解释上文中的 gif
图函数执行程序。
测试用例中数组的最终程序是[1,2,3,4,5,6]
。
3.1.3 简化 compose 便于了解
本人入手调试之后,你会发现 compose
执行后就是相似这样的构造(省略 try catch
判断)。
// 这样就可能更好了解了。// simpleKoaComposeconst [fn1, fn2, fn3] = stack;const fnMiddleware = function(context){ return Promise.resolve( fn1(context, function next(){ return Promise.resolve( fn2(context, function next(){ return Promise.resolve( fn3(context, function next(){ return Promise.resolve(); }) ) }) ) }) );};
也就是说koa-compose
返回的是一个Promise
,从中间件(传入的数组)
中取出第一个函数,传入context
和第一个next
函数来执行。
第一个next
函数里也是返回的是一个Promise
,从中间件(传入的数组)
中取出第二个函数,传入context
和第二个next
函数来执行。
第二个next
函数里也是返回的是一个Promise
,从中间件(传入的数组)
中取出第三个函数,传入context
和第三个next
函数来执行。
第三个...
以此类推。最初一个中间件中有调用next
函数,则返回Promise.resolve
。如果没有,则不执行next
函数。
这样就把所有中间件串联起来了。这也就是咱们常说的洋葱模型。
不得不说十分惊艳,“玩还是大神会玩”。
3.2 谬误捕捉
it('should catch downstream errors', async () => { const arr = [] const stack = [] stack.push(async (ctx, next) => { arr.push(1) try { arr.push(6) await next() arr.push(7) } catch (err) { arr.push(2) } arr.push(3) }) stack.push(async (ctx, next) => { arr.push(4) throw new Error() }) await compose(stack)({}) // 输入程序 是 [ 1, 6, 4, 2, 3 ] expect(arr).toEqual([1, 6, 4, 2, 3])})
置信了解了第一个测试用例和 compose
函数,也是比拟好了解这个测试用例了。这一部分其实就是对应的代码在这里。
try { return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))} catch (err) { return Promise.reject(err)}
3.3 next 函数不能调用屡次
it('should throw if next() is called multiple times', () => { return compose([ async (ctx, next) => { await next() await next() } ])({}).then(() => { throw new Error('boom') }, (err) => { assert(/multiple times/.test(err.message)) })})
这一块对应的则是:
index = -1dispatch(0)function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i}
调用两次后 i
和 index
都为 1
,所以会报错。
compose/test/test.js
文件中总共 300余行,还有很多测试用例能够依照文中办法自行调试。
4. 总结
尽管koa-compose
源码 50行 不到,但如果是第一次看源码调试源码,还是会有难度的。其中混杂着高阶函数、闭包、Promise
、bind
等基础知识。
通过本文,咱们相熟了 koa-compose
中间件常说的洋葱模型,学会了局部 jest
用法,同时也学会了如何应用现成的测试用例去调试源码。
置信学会了通过测试用例调试源码后,会感觉源码也没有设想中的那么难。
开源我的项目,个别都会有很全面的测试用例。除了能够给咱们学习源码调试源码带来不便的同时,也能够给咱们带来的启发:本人工作中的我的项目,也能够逐渐引入测试工具,比方 jest
。
此外,读开源我的项目源码是咱们学习业界大牛设计思维和源码实现等比拟好的形式。
看完本文,十分心愿能本人入手实际调试源码去学习,容易排汇消化。另外,如果你有余力,能够持续看我的 koa-compose
源码文章:学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理