2023.26 koa创建实例和请求流程
大家好,我是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
内部代码,这里是怎么回调到的不知道?
通过监听函数实现的。