koa中间件原理 之【手写koa】

1. 早期没有koa之前代码是如何工作的?

早期没有koaexpess之前,所有的请求响应只能在http.createServer中完成

const http = require('http')
const server = http.createServer((req, res)=>{
 //早期在这里面判断 req.url(请求路径) 来实现返回不同的数据
 if (req.url == "/") {
    res.writeHead(200, { "Content-Type": "text/html" }) //返回请求状态码
    res.end("返回根目录页面给浏览器")
  } else if (req.url == "/about") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end("返回/about目录页面给浏览器")
  }
})
server.listen(3000,()=>{
 console.log('监听端⼝3000') //服务器开启成功的回调函数
})

2. 有koa中间件后代码的工作方式

通过执行app.use(fn)传入一个fn函数作为参数,而这个函数就是一个中间件

const Koa = require('koa')
const app = new Koa() // 创建koa服务, koa内部是封装http实现的

app.use(async (ctx, next) => {
    console.log(1)
    await next()
    console.log(4)
})

app.use(async (ctx, next) => {
    console.log(2)
    await next()
    console.log(3)
})

app.listen(3000) // 监听服务

//执行结果: 1, 2, 3, 4

上面的意思是:浏览器访问一个请求过来会经过1, 2, 3, 4 这4个地方的不同处理,从而返回一个最终的数据给浏览器。 koa与http相比 意义在于更分层次,更有逻辑性的处理了浏览器的请求

koa中间件的执行过程类似一个切洋葱的过程,洋葱上一半由外到内一层一层切开(执行),下一半由内到外一层一层切开(执行), 所以我们称 koa中间件 模型为 “洋葱模型”。
在这里插入图片描述

3. 如何自己实现一个koa呢?

我们知道koa是利用 洋葱模型原理 通过封装http实现的,下面我们也来实现一个koa吧!

const http = require('http')

class Koa {
  constructor() {
    this.middlewares = []  //存储 koa.use(fn) 传入的中间件函数 fn
  }
  //当执行 koa.listen(3000) 方法时创建一个 http 服务
  listen(...args) {
    const server = http.createServer(async (req, res) => {
      const ctx = this.createContext(req, res)  //1. 得到 ctx 封装好的请求相应参数
      const fn = this.compose(this.middlewares) //2. 通过高阶函数原理将 ctx 传入中间件中
      await fn(ctx)                             //3. 执行所有的中间件方法
      res.end(ctx.body)                         //4. 最终给浏览器返回数据
    })
    server.listen(...args)
  }
  use(middleware) {
    this.middlewares.push(middleware)
  }
  createContext(req, res) {
    //选择性暴露 get set 方法 目的不让用户随意修改属性
    const ctx = Object.create({
      get url() {
        return this.request.url
      },
      get body() {
          return this.response.body
      },
      set body(val){
          this.response.body = val
      },
      get method() {
          return this.request.method
      }
    })

    ctx.request = Object.create({
      get url(){
        return this.req.url
      },
      get method(){
        return this.req.method.toLowerCase()
      }
    })

    ctx.response = Object.create({
      get body(){
        return this._body
      },
      set body(val){
        this._body = val
      }
    })

    ctx.req = ctx.request.req = req
    ctx.res = ctx.response.res = res
    return ctx
  }
  //洋葱模型核心原理
  compose(middlewares) {
    return function (ctx) {
      return dispatch(0)
      function dispatch(i) {
        let fn = middlewares[i]
        if (!fn) {
          return Promise.resolve()
        }
        // dispatch 方法返回 Promise 的原因是确保洋葱模型存在异步情况下可以使用 await语法 让洋葱模型依然按照顺序执行
        return Promise.resolve(
          fn(ctx, function next() {
            //函数循环递归时:上一个函数要等到下一个函数执行完成才算执行完成 这就是洋葱模型的核心原理
            return dispatch(i + 1)
          })
        )
      }
    }
  }
}

//下面我们使用自己实现的 Koa
const app = new Koa() // 创建koa服务, koa内部是封装http实现的

app.use(async (ctx, next) => {
    console.log(1)
    await next()
    console.log(4)
});
app.use(async (ctx, next) => {
    console.log(2)
    await delay() //测试异步后代码是否可以按照顺序执行
    await next()
    console.log(3)
});

function delay() {
  return new Promise(reslove => {
    setTimeout(() => {
      console.log('我是中途的异步代码')
      reslove()
    }, 2000)
  })
}

app.listen(3000, ()=>{
  console.log('监听端⼝3000') //服务器开启成功的回调函数
})

运行结果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值