美文网首页
2023.26 koa创建实例和请求流程

2023.26 koa创建实例和请求流程

作者: wo不是黄蓉 | 来源:发表于2023-07-21 17:37 被阅读0次

大家好,我是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中什么时候会用到事件机制?

  1. 中间件处理:Koa 中的中间件就是利用事件机制来实现的。当请求进入 Koa 应用时,Koa 会按照中间件的声明顺序依次触发相应的事件,在事件的回调函数中进行相应的处理。
  2. 错误处理:Koa 通过 app.on('error', callback) 事件来捕获应用中可能发生的错误,然后进行相应的处理。
  3. 自定义事件:通过 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创建服务是通过nodehttp模块进行的,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);
  }

虽然,callbackconst 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内部代码,这里是怎么回调到的不知道?

通过监听函数实现的。

相关文章

网友评论

      本文标题:2023.26 koa创建实例和请求流程

      本文链接:https://www.haomeiwen.com/subject/iaptpdtx.html