大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。
入口文件
package.json->"main": "lib/application.js",
源码调试
新建一个项目,安装koa模块,创建一个简单的应用,开启debug模式就可以开始调试了
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
// app.emit('sayHello', 'hello world')
console.log('1')//第一次执行打印1
await next() //遇到next调用下一个中间件函数打印3
console.log('2') //执行完毕,回到上一个中间件,打印2
ctx.body = 'Hello World'
})
app.use(async (ctx) => {
console.log('3')
ctx.body = 'hello'
})
// app.on('sayHello', (args) => {
// console.log(111, args)
// })
app.listen(3000)
打印顺序: 1 3 2
阅读准备
需要了解几个模块,知道这写模块是干啥用的
const Emitter = require('events');
使用events模块实现发布订阅模式,koa中什么时候会用到事件机制?
- 中间件处理:Koa 中的中间件就是利用事件机制来实现的。当请求进入 Koa 应用时,Koa 会按照中间件的声明顺序依次触发相应的事件,在事件的回调函数中进行相应的处理。
- 错误处理:Koa 通过
app.on('error', callback)事件来捕获应用中可能发生的错误,然后进行相应的处理。 - 自定义事件:通过 Koa 的
context对象或其他对象的实例(如app)提供的emit()方法,可以触发自定义事件,并在相应的回调函数中处理事件。
const delegate = require('delegates')
delegates 帮我们快捷地使用设计模式当中的委托模式,即外层暴露的对象将请求委托为内部的其他对象进行处理
const isGeneratorFunction = require('is-generator-function');
isGeneratorFunction 判断一个函数是否为Generator函数
const onFinished = require('on-finished')
确保一个流在关闭、完成和报错时都会执行相应的回调函数
创建流程
koa的创建流程很简单,就是创建一个实例,保存中间件,执行app.listen方法,保存中间件是在app.use中执行的。
use (fn) {
this.middleware.push(fn)
return this
}
app.listen的时候会会调用callback函数和当前实例进行关联。koa创建服务是通过node的http模块进行的,http.createServer创建Http服务器,传参为requestListener作为request事件的requestListener监听函数,请求处理函数会自动添加到request事件,会自动传两个参数req,res
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
然后回到callback函数,执行compose函数,返回this.handleRequest,第一次执行:callback() 准备好了fn,但是没有执行,此时只是准备好了请求处理函数,当发起请求时才会执行到handleRequest代码。
handleRequest创建上下文并且返回处理请求函数,handleRequest函数第一次是不会执行的,这个函数是被requestListener监听的,只有在发起请求时才会调用的
callback() {
//执行compose函数
const fn = compose(this.middleware);
if (!this.listenerCount('error')) this.on('error', this.onerror);
//以下内部代码只有在发起请求时才会执行
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
发起请求handleRequest
当发起请求时会调用callback中的requestListener函数,此时会创建上下文并且返回请求handleRequest函数,其中传了两个参数ctx,fn,fn真正返回的内容为
function(context,next){let index = -1;return dispatch(0)}
也就是dispatch(0)的执行结果,返回以下内容
//fn为第i个中间件,也就是middleware[0],传入上下文和dispatch函数,也就是app.use中的第二个参数next
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
遇到next也就是再调一次dispatch函数,此时执行dispatch(1),进入第二个中间件,执行完后返回,当执行完next后就会触发await向下继续执行,这样就可以实现代码异步执行了
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
res.statusCode = 404;
//绑定异常事件处理函数
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
虽然,callback中const fn = compose(this.middleware)传入的是一个数组,但是可以根据里面的i来决定每次调用哪个中间件函数
compose函数具体分析如下,关于compose也推荐这篇文章
let index = -1
return dispatch(0)
function dispatch (i) {
//当i<=index时,说明在同一个中间件中next了多次提示报错
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
//如果i===middleware.length,fn为undefined,返回
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
//如果有,返回一个promise,并且将当前中间件函数传入,然后dispatch传入i+1,如果后面还有中间件就可以在handleRequest中继续执行第二个中间件函数,这样就有一点理解洋葱模型了
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
当发送请求时会执行HandleRequest内部代码,这里是怎么回调到的不知道?
通过监听函数实现的。











网友评论