开源软件名称: lxchuan12/koa-analysis开源软件地址: https://github.com/lxchuan12/koa-analysis开源编程语言:
JavaScript
96.1%
开源软件介绍: 学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理
1. 前言
你好,我是若川 ,微信搜索「若川视野」 关注我,专注前端技术分享。欢迎加我微信ruochuan12
,加群交流学习。
这是学习源码整体架构系列
第七篇。整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是主线的具体函数的实现。本篇文章学习的是实际仓库的代码。
本文仓库地址 :git clone https://github.com/lxchuan12/koa-analysis.git
要是有人说到怎么读源码,正在读文章的你能推荐我的源码系列文章,那真是太好了 。
学习源码整体架构系列
文章如下:
1.学习 jQuery 源码整体架构,打造属于自己的 js 类库
2.学习 underscore 源码整体架构,打造属于自己的函数式编程类库
3.学习 lodash 源码整体架构,打造属于自己的函数式编程类库
4.学习 sentry 源码整体架构,打造属于自己的前端异常监控SDK
5.学习 vuex 源码整体架构,打造属于自己的状态管理库
6.学习 axios 源码整体架构,打造属于自己的请求库
7.学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理
8.学习 redux 源码整体架构,深入理解 redux 及其中间件原理
感兴趣的读者可以点击阅读。
其他源码计划中的有:express
、vue-rotuer
、react-redux
等源码,不知何时能写完(哭泣),欢迎持续关注我(若川)。
源码类文章,一般阅读量不高。已经有能力看懂的,自己就看了。不想看,不敢看的就不会去看源码。
所以我的文章,尽量写得让想看源码又不知道怎么看的读者能看懂。
如果你简历上一不小心写了熟悉koa
,面试官大概率会问:
1、koa
洋葱模型怎么实现的。
2、如果中间件中的next()
方法报错了怎么办。
3、co
的原理是怎样的。
等等问题
导读
文章通过例子调试koa
,梳理koa
的主流程,来理解koa-compose
洋葱模型原理和co
库的原理,相信看完一定会有所收获。
本文学习的koa
版本是v2.11.0
。克隆的官方仓库的master
分支。
截至目前(2020年3月11日),最新一次commit
是2020-01-04 07:41 Olle Jonsson
eda27608
,build: Drop unused Travis sudo: false directive (#1416)
。
本文仓库在这里若川的 koa-analysis github 仓库 https://github.com/lxchuan12/koa-analysis 。求个star
呀。
2. 本文阅读最佳方式
先star
一下我的仓库,再把它git clone https://github.com/lxchuan12/koa-analysis.git
克隆下来。不用管你是否用过nodejs
。会一点点promise、generator、async、await
等知识即可看懂。如果一点点也不会,可以边看阮一峰老师的《ES6标准入门》 相关章节。跟着文章节奏调试和示例代码调试,动手调试(用vscode
或者chrome
)印象更加深刻 。文章长段代码不用细看,可以调试时再细看。看这类源码文章百遍,可能不如自己多调试几遍。也欢迎加我微信交流ruochuan12
。
# 克隆我的这个仓库
git clone https://github.com/lxchuan12/koa-analysis.git
# chrome 调试:
# 全局安装 http-server
npm i -g http-server
hs koa/examples/
# 可以指定端口 -p 3001
# hs -p 3001 koa/examples/
# 浏览器中打开
# 然后在浏览器中打开localhost:8080,开心的把代码调试起来
这里把这个examples
文件夹做个简单介绍。
middleware
文件夹是用来vscode
调试整体流程的。
simpleKoa
文件夹是koa
简化版,为了调试koa-compose
洋葱模型如何串联起来各个中间件的。
koa-convert
文件夹是用来调试koa-convert
和co
源码的。
co-generator
文件夹是模拟实现co
的示例代码。
3. vscode 调试 koa 源码方法
之前,我在知乎回答了一个问题一年内的前端看不懂前端框架源码怎么办?
推荐了一些资料,阅读量还不错,大家有兴趣可以看看。主要有四点:
1.借助调试
2.搜索查阅相关高赞文章
3.把不懂的地方记录下来,查阅相关文档
4.总结
看源码,调试很重要,所以我详细写下 koa
源码调试方法,帮助一些可能不知道如何调试的读者。
# 我已经克隆到我的koa-analysis仓库了
git clone https://github.com/koajs/koa.git
// package.json
{
"name" : " koa" ,
"version" : " 2.11.0" ,
"description" : " Koa web app framework" ,
"main" : " lib/application.js" ,
}
克隆源码后,看package.json
找到main
,就知道入口文件是lib/application.js
了。
大概看完项目结构后发现没有examples
文件夹(一般项目都会有这个文件夹,告知用户如何使用该项目),这时仔细看README.md
。
如果看英文README.md
有些吃力,会发现在Community
标题下有一个中文文档 v2.x 。同时也有一个examples
仓库 。
# 我已经克隆下来到我的仓库了
git clone https://github.com/koajs/examples.git
这时再开心的把examples
克隆到自己电脑。可以安装好依赖,逐个研究学习下这里的例子,然后可能就一不小心掌握了koa
的基本用法。当然,我这里不详细写这一块了,我是自己手写一些例子来调试。
继续看文档会发现使用指南 讲述编写中间件
。
3.1 使用文档中的中间件koa-compose
例子来调试
学习 koa-compose
前,先看两张图。
在koa
中,请求响应都放在中间件的第一个参数context
对象中了。
再引用Koa中文文档 中的一段:
如果您是前端开发人员,您可以将 next()
; 之前的任意代码视为“捕获”阶段,这个简易的 gif
说明了 async
函数如何使我们能够恰当地利用堆栈流来实现请求和响应流:
创建一个跟踪响应时间的日期
等待下一个中间件的控制
创建另一个日期跟踪持续时间
等待下一个中间件的控制
将响应主体设置为“Hello World”
计算持续时间
输出日志行
计算响应时间
设置 X-Response-Time
头字段
交给 Koa 处理响应
读者们看完这个gif图,也可以思考下如何实现的。根据表现,可以猜测是next
是一个函数,而且返回的可能是一个promise
,被await
调用。
看到这个gif
图,我把之前写的examples/koa-compose
的调试方法含泪删除 了。默默写上gif
图上的这些代码,想着这个读者们更容易读懂。
我把这段代码写在这里 koa/examples/middleware/app.js
便于调试。
在项目路径下配置新建.vscode/launch.json 文件,program
配置为自己写的koa/examples/middleware/app.js
文件。
.vscode/launch.json 代码,点击这里展开/收缩,可以复制
{
"version" : " 0.2.0" ,
"configurations" : [
{
"type" : " node" ,
"request" : " launch" ,
"name" : " 启动程序" ,
"skipFiles" : [
" <node_internals>/**"
],
"program" : " ${workspaceFolder}/koa/examples/middleware/app.js"
}
]
}
按F5键
开始调试,调试时先走主流程,必要的地方打上断点,不用一开始就关心细枝末节。
断点调试要领:
赋值语句可以一步跳过,看返回值即可,后续详细再看。
函数执行需要断点跟着看,也可以结合注释和上下文倒推这个函数做了什么。
上述比较啰嗦的写了一堆调试方法。主要是想着授人予鱼不如授人予渔
,这样换成其他源码也会调试了。
简单说下chrome
调试nodejs
,chrome
浏览器打开chrome://inspect
,点击配置**configure...**配置127.0.0.1:端口号
(端口号在Vscode
调试控制台显示了)。
更多可以查看English Debugging Guide
中文调试指南
喜欢看视频的读者也可以看慕课网这个视频node.js调试入门 ,讲得还是比较详细的。
不过我感觉在chrome
调试nodejs
项目体验不是很好(可能是我方式不对),所以我大部分具体的代码时都放在html
文件script
形式,在chrome
调试了。
4. 先看看 new Koa()
结果app
是什么
看源码我习惯性看它的实例对象结构 ,一般所有属性和方法都放在实例对象上了,而且会通过原型链查找形式查找最顶端的属性和方法。
用koa/examples/middleware/app.js
文件调试时,先看下执行new Koa()
之后,app
是什么,有个初步印象。
// 文件 koa/examples/middleware/app.js
const Koa = require ( '../../lib/application' ) ;
// const Koa = require('koa');
// 这里打个断点
const app = new Koa ( ) ;
// x-response-time
// 这里打个断点
app . use ( async ( ctx , next ) => {
} ) ;
在调试控制台ctrl + 反引号键(一般在
Tab上方的按键)唤起
,输入app
,按enter
键打印app
。会有一张这样的图。
VScode
也有一个代码调试神器插件Debug Visualizer
。
安装好后插件后,按ctrl + shift + p
,输入Open a new Debug Visualizer View
,来使用,输入app
,显示是这样的。
不过目前体验来看,相对还比较鸡肋,只能显示一级,而且只能显示对象,相信以后会更好。更多玩法可以查看它的文档。
我把koa实例对象比较完整的用xmind
画出来了,大概看看就好,有个初步印象。
接着,我们可以看下app 实例、context、request、request
的官方文档。
4.1 app 实例、context、request、request 官方API文档
可以真正使用的时候再去仔细看文档。
5. koa 主流程梳理简化
通过F5启动调试(直接跳到下一个断点处)
、F10单步跳过
、F11单步调试
等,配合重要的地方断点,调试完整体代码,其实比较容易整理出如下主流程的代码。
class Emitter {
// node 内置模块
constructor ( ) {
}
}
class Koa extends Emitter {
constructor ( options ) {
super ( ) ;
options = options || { } ;
this . middleware = [ ] ;
this . context = {
method : 'GET' ,
url : '/url' ,
body : undefined ,
set : function ( key , val ) {
console . log ( 'context.set' , key , val ) ;
} ,
} ;
}
use ( fn ) {
this . middleware . push ( fn ) ;
return this ;
}
listen ( ) {
const fnMiddleware = compose ( this . middleware ) ;
const ctx = this . context ;
const handleResponse = ( ) => respond ( ctx ) ;
const onerror = function ( ) {
console . log ( 'onerror' ) ;
} ;
fnMiddleware ( ctx ) . then ( handleResponse ) . catch ( onerror ) ;
}
}
function respond ( ctx ) {
console . log ( 'handleResponse' ) ;
console . log ( 'response.end' , ctx . body ) ;
}
重点就在listen
函数里的compose
这个函数,接下来我们就详细来欣赏 下这个函数。
6. koa-compose 源码(洋葱模型实现)
通过app.use()
添加了若干函数,但是要把它们串起来执行呀。像上文的gif
图一样。
compose
函数,传入一个数组,返回一个函数。对入参是不是数组和校验数组每一项是不是函数。
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!' )
}
// 传入对象 context 返回Promise
return function ( context , next ) {
// last called middleware #
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 , dispatch . bind ( null , i + 1 ) ) ) ;
} catch ( err ) {
return Promise . reject ( err )
}
}
}
}
把简化的代码和koa-compose
代码写在了一个文件中。koa/examples/simpleKoa/koa-compose.js
hs koa/examples/
# 然后可以打开localhost:8080/simpleKoa,开心的把代码调试起来
不过这样好像还是有点麻烦,我还把这些代码放在codepen
https://codepen.io/lxchuan12/pen/wvarPEb 中,直接可以在线调试啦 。是不是觉得很贴心^_^,自己多调试几遍便于消化理解。
你会发现compose
就是类似这样的结构(移除一些判断)。
// 这样就可能更好理解了。
// simpleKoaCompose
const [ fn1 , fn2 , fn3 ] = this . middleware ;
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 ( ) ;
} )
)
} )
)
} )
) ;
} ;
fnMiddleware ( ctx ) . then ( handleResponse ) . catch ( onerror ) ;
也就是说koa-compose
返回的是一个Promise
,Promise
中取出第一个函数(app.use
添加的中间件),传入context
和第一个next
函数来执行。
第一个next
函数里也是返回的是一个Promise
,Promise
中取出第二个函数(app.use
添加的中间件),传入context
和第二个next
函数来执行。
第二个next
函数里也是返回的是一个Promise
,Promise
中取出第三个函数(app.use
添加的中间件),传入context
和第三个next
函数来执行。
第三个...
以此类推。最后一个中间件中有调用next
函数,则返回Promise.resolve
。如果没有,则不执行next
函数。
这样就把所有中间件串联起来了。这也就是我们常说的洋葱模型。
不得不说非常惊艳,“玩还是大神会玩” 。
这种把函数存储下来的方式,在很多源码中都有看到。比如lodash
源码的惰性求值,vuex
也是把action
等函数存储下,最后才去调用。
搞懂了koa-compose
洋葱模型实现的代码,其他代码就不在话下了。
7. 错误处理
中文文档 错误处理
仔细看文档,文档中写了三种捕获错误的方式。
// application.js 文件
class Application extends Emitter {
// 代码有简化组合
listen ( ) {
const fnMiddleware = compose ( this . middleware ) ;
if ( ! this . listenerCount ( 'error' ) ) this . on ( 'error' , this . onerror ) ;
const onerror = err => ctx . onerror ( err ) ;
fnMiddleware ( ctx ) . then ( handleResponse ) . catch ( onerror ) ;
}
onerror ( err ) {
// 代码省略
// ...
}
}
ctx.onerror
lib/context.js
文件中,有一个函数onerror
,而且有这么一行代码this.app.emit('error', err, this)
。
module . exports = {
onerror ( ) {
// delegate
// app 是在new Koa() 实例
this . app . emit ( 'error' , err , this ) ;
}
}
app . use ( async ( ctx , next ) => {
try {
await next ( ) ;
} catch ( err ) {
err . status = err . statusCode || err . status || 500 ;
throw err ;
}
} ) ;
try catch
错误或被fnMiddleware(ctx).then(handleResponse).catch(onerror);
,这里的onerror
是ctx.onerror
而ctx.onerror
函数中又调用了this.app.emit('error', err, this)
,所以在最外围app.on('error',err => {})
可以捕获中间件链中的错误。
因为koa
继承自events模块
,所以有'emit'和on
等方法)
8. koa2 和 koa1 的简单对比
中文文档中描述了 koa2 和 koa1 的区别
koa1
中主要是generator
函数。koa2
中会自动转换generator
函数。
// Koa 将转换
app . use ( function * ( next ) {
const start = Date . now ( ) ;
yield next ;
const ms = Date . now ( ) - start ;
console . log ( `${ this . method } ${ this . url } - ${ ms } ms` ) ;
} ) ;
8.1 koa-convert 源码
在vscode/launch.json
文件,找到这个program
字段,修改为"program": "${workspaceFolder}/koa/examples/koa-convert/app.js"
。
通过F5启动调试(直接跳到下一个断点处)
、F10单步跳过
、F11单步调试
调试走一遍流程。重要地方断点调试。
app.use
时有一层判断,是否是generator
函数,如果是则用koa-convert
暴露的方法convert
来转换重新赋值,再存入middleware
,后续再使用。
class Koa extends Emitter {
use ( fn ) {
if ( typeof fn !== 'function' ) throw new TypeError ( 'middleware must be a function!' ) ;
if ( isGeneratorFunction ( fn ) ) {
deprecate ( 'Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md' ) ;
fn = convert ( fn ) ;
}
debug ( 'use %s' , fn . _name || fn . name || '-' ) ;
this . middleware . push ( fn ) ;
return this ;
}
}
koa-convert
源码挺多,核心代码其实是这样的。
function convert ( ) {
return function ( ctx , next ) {
return co . call ( ctx , mw . call ( ctx , createGenerator ( next ) ) )
}
function *
六六分期app的软件客服如何联系?不知道吗?加qq群【895510560】即可!标题:六六分期
阅读:18295| 2023-10-27
今天小编告诉大家如何处理win10系统火狐flash插件总是崩溃的问题,可能很多用户都不知
阅读:9687| 2022-11-06
今天小编告诉大家如何对win10系统删除桌面回收站图标进行设置,可能很多用户都不知道
阅读:8184| 2022-11-06
今天小编告诉大家如何对win10系统电脑设置节能降温的设置方法,想必大家都遇到过需要
阅读:8553| 2022-11-06
我们在使用xp系统的过程中,经常需要对xp系统无线网络安装向导设置进行设置,可能很多
阅读:8463| 2022-11-06
今天小编告诉大家如何处理win7系统玩cf老是与主机连接不稳定的问题,可能很多用户都不
阅读:9399| 2022-11-06
电脑对日常生活的重要性小编就不多说了,可是一旦碰到win7系统设置cf烟雾头的问题,很
阅读:8434| 2022-11-06
我们在日常使用电脑的时候,有的小伙伴们可能在打开应用的时候会遇见提示应用程序无法
阅读:7869| 2022-11-06
今天小编告诉大家如何对win7系统打开vcf文件进行设置,可能很多用户都不知道怎么对win
阅读:8419| 2022-11-06
今天小编告诉大家如何对win10系统s4开启USB调试模式进行设置,可能很多用户都不知道怎
阅读:7398| 2022-11-06
请发表评论