You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 的引用
return function (context, next) {
// 这里的 context 参数是作为一个全局的设置, 所有中间件的第一个参数就是传入的 context, 这样可以
// 在 context 中对某个值或者某些值做"洋葱处理"
// 解释一下传入的 next, 这个传入的 next 函数是在所有中间件执行后的"最后"一个函数, 这里的"最后"并不是真正的最后,
// 而是像上面那个图中的圆心, 执行完圆心之后, 会返回去执行上一个中间件函数(middleware[length - 1])剩下的逻辑
// index 是用来记录中间件函数运行到了哪一个函数
let index = -1
// 执行第一个中间件函数
return dispatch(0)
function dispatch (i) {
// i 是洋葱模型的记录已经运行的函数中间件的下标, 如果一个中间件里面运行两次 next, 那么 i 是会比 index 小的.
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) {
// 这里的 next 就是一开始 compose 传入的 next, 意味着当中间件函数数列执行完后, 执行这个 next 函数, 即圆心
fn = next
}
// 如果没有函数, 直接返回空值的 Promise
if (!fn) return Promise.resolve()
try {
// next 函数是固定的, 可以执行下一个函数
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
简介
与express既当爹又当妈相比,koa不要太简洁。因为它只实现了基础核心,需要其他功能时额外引入即可。
它有多简介呢?查看下它的源码就知道了:
整个的实现就4个文件,对比下express:
简直不能太友好呀!
从一个简单的例子开始
大家觉得会输出什么呢?
是 1234? 或者 1324 ?
都不是,答案是 1 3 4 2。
很多新手都会觉得没法理解,那么接下来通过这个例子来解析koa的源码,顺便解答为什么会这样输出。
application.js
从koa源码package.json的main入口可以看到,它指向的是lib/application.js。即整个应用的入口。
构造函数
const app = new koa()时,会处理构造函数的逻辑:
构造函数主要的功能是初始化了中间件的容器(可看出koa中间件就是用数组处理的),从context,request,response创建koa proto的相同功能属性。
中间件添加
app.use(xxx) 对应的逻辑如下:
很简单,就是添加到this.middleware数组里。
创建服务器并监听
app.listen(3000) 对应的逻辑如下:
可以看到,里面使用的是http.createServer来创建。重点是里面的callbak逻辑。
很明显,http.createServer的回调代理给了这里的handleRequest。同时可以看到里面处理了中间件逻辑、每次请求的上下文、请求的最终处理等,那么问题来了:
问题一:都说koa中间件是洋葱模型,那么这里是如何实现的呢?
对应上面的逻辑代码:
其中的compose是koa-compose。让我们来看看它的源码实现:
整个中间件的处理注释里面写的很清楚了,总结几点:
1.洋葱模型(即先入后出)是基于中间件中使用 next()实现的,如果中间件没有使用next(),或者某些中间件没有使用,则它及它后面的中间件就会被截断掉,执行不到了。
2.洋葱模型实现的关键点在于下面代码:
即当前中间件中执行nex(),便会递归处理后面的中间件,等待后面的中间件执行完,才会再回到当前中间件,实现洋葱的效果。
3.洋葱模型并不是绝对的,可以在中间件的nex()前后执行需要的逻辑,实现AOP的效果。比如接口的权限验证,必须是在next()之前进行验证,只有验证通过了才会去执行后面的中间件。
问题二:每次请求的上下文是如何处理的?
对应上面的 const ctx = this.createContext(req, res)。从这句代码中,可以看出来,每次请求都会根据req和res创建一个全新的上下文ctx,那么是如何实现的呢?这里的ctx中包含哪些东西呢?
从上面可以看到,app、req、res等等全部赋给了context一个对象上面。所以我们才能够访问ctx.req.url、ctx.res.body这些属性。那为什么app、req、res、ctx也存放在了request、和response对象中呢?
使它们同时共享一个app、req、res、ctx,是为了将处理职责进行转移,当用户访问时,只需要ctx就可以获取koa提供的所有数据和方法,而koa会继续将这些职责进行划分,比如request是进一步封装req的,response是进一步封装res的,这样职责得到了分散,降低了耦合度,同时共享所有资源使context具有高内聚的性质,内部元素互相能访问到。
问题三:每次请求的最终回调处理是怎样的?
对应上面的this.handleRequest(ctx, fn),源码如下:
这里可以看出,会先执行所有的中间件,如果出错去执行onerror,如果成功回去执行handleResponse。而handleResponse的respond的逻辑如下:
其核心就是根据不同类型的数据对 http 的响应头部与响应体 body 做对应的处理.运用 node http 模块中的响应对象中的 end 方法与 koa context 对象中代理的属性进行最终响应对象的设置.
至此,整个appllication.js的核心实现基本分析完了。
context.js
这个js主要实现的是koa的上下文。它主要实现两个核心功能:
上面分析application代码时,有这么一段:
这里处理的是所有中间件,如果出错则用onerror去处理,里面的实现逻辑使用的ctx.onerror。而ctx.onerror的源码如下:
可见,最终会将err还是代理回app上,所以可以通过如下的方式监听整个的错误进行处理:
context中还有如下两端代码,使用的是依靠delegates库通过委托模式,将node内部的request和response委托到了context上:
所以,我们可以通过如下访问:
request.js和response.js
比较简单,参考图
request
response
参考
koa源码解析
The text was updated successfully, but these errors were encountered: