一文搞懂KOA2源码

273 阅读2分钟

koa源码打开有四个文件:

  • application.js是核心文件;
  • context.js是对ctx的封装;
  • request.js是对请求的封装;
  • response.js是对ctx.body的封装;

1.app.use的简单实现

const app = {}
app.middlewares = []
app.use = function(cb){
    app.middlewares.push(cb)
}
function dispath(index) {
    if (index === app.middlewares.length) { return () => {}}
    const route = app.middlewares[index]
    route(() => dispath(index + 1))
}
app.use((next) => {
    console.log(1)
    next()
    console.log(5)
})
app.use((next) => {
    console.log(2)
    next()
    console.log(4)
})
app.use(() => {
    console.log(3)
})
dispath(0)

😂,发现没😄上边这简单的代码就实现了洋葱模型;把传到app.use的函数先用数组保存起来,然后再依次遍历,执行 还可以用下边这种方式实现:

const app = {}
app.middlewares = []
app.use = function(cb){
    app.middlewares.push(cb)
}
app.use((next) => {
    console.log(1)
    next()
    console.log(5)
})
app.use((next) => {
    console.log(2)
    next()
    console.log(4)
})
app.use((next) => {
    console.log(3)
    next()
})
// 这里的写法很像redux源码,`...args就是fn传的参数;pre函数在执行的时候,传了个空函数进去,执行了next函数,这这这...太棒了`
const fn = app.middlewares.reduce((pre,next) => (...args) => pre(() => next(...args)))
fn(() => {})

koa 简单版本

application文件

const Emitter = require('events')
const http = require('http')
const Stream = require('stream')
const request = require('./request')
const response = require('./response')
const context = require('./context')
class Application extends Emitter{
  constructor () {
    super()
    this.middleware = []
    // 使用 Object.create(),拥有了原有的功能,仅仅拥有了原有的功能,将对象原型链上hasOwnProperty等属性去掉了
    this.request = Object.create(request)
    this.response = Object.create(response)
    this.context = Object.create(context)
  }
  createContext (req, res) {
    const ctx = this.context
    ctx.request = this.request // ctx.request ctx.response是koa封装的
    ctx.response = this.response
    ctx.req = ctx.request.req = req // ctx.req ctx.res是默认的请求和响应
    ctx.res = ctx.request.res = res
    return ctx
  }
   // 中间件组合
  compose(ctx, middleware) { //处理了promise的逻辑
    function dispath(index) {
      if (index === middleware.length) { return Promise.resolve()}
      const middlewareitem = middleware[index]
      return Promise.resolve(middlewareitem(ctx, () => dispath(index + 1)))
    }
    return dispath(0)
  }
  // 处理request请求
  handleRequest (req, res) {
    // 先创建上下文
    const ctx = this.createContext(req, res)
    res.statusCode = 404
    // 再把所有中间件进行组合
    let p = this.compose(ctx, this.middleware)
    p.then( () => {
      const body = ctx.body
      if (body instanceof Stream) {
        // res.setHeader('Content-Disposition','attachment')
        body.pipe(res)
      } else if(typeof body === 'number') {
        res.setHeader('Content-Type', 'text/plain;charset=utf8')
        res.end(body.toString())
      } else if (typeof body === 'object') {
        res.setHeader('Content-Type', 'application/json;charset=utf8')
        res.end(JSON.stringify(body))
      } else if(typeof body === 'string' || Buffer.isBuffer(body)) {
        res.setHeader('Content-Type', 'text/plain;charset=utf8')
        res.end(body)
      } else {
        res.end(`not found`)
      }
    }).catch((e) => {
      this.emit(`error`, e)
    })
  }
  // 收集中间件
  use (fn) {
    this.middleware.push(fn)
  }
  // 创建服务并监听端口号
  listen (...args) {
    const server = http.createServer(this.handleRequest.bind(this))
    server.listen(...args)
  }
}
module.exports = Application

context文件

const proto = {};
function defineGetter(property, key) {
  // Object.prototype.__defineGetter__()
  // 已废弃;方法可以将一个函数绑定在当前对象的指定属性上,当那个属性的值被读取时,你所绑定的函数就会被调用。
  // __defineSetter__同理
  // koa源码用的是delegates这个npm包,这个npm包内部也是用的__defineGetter__,和__defineSetter__
  proto.__defineGetter__(key, function() {
    return this[property][key];
  });
}
function defineSetter(property, key) {
  proto.__defineSetter__(key, function(value) {
    this[property][key] = value
  });
}
defineGetter("request", "path");
defineGetter('request','url')
defineGetter('response','body')
defineSetter('response','body')
module.exports = proto;

request文件

const url = require("url");
module.exports = {
  // 访问器的写法
  get url() {
    return this.req.url;
  },
  get path() {
    const { pathname } = url.parse(this.req.url, true);
    return pathname;
  }
};

response文件

module.exports = {
  set body(val) {
    console.log(this)
    // this.res.statusCode = 200
    this._body = val
  },
  get body() {
    return this._body
  }
}

简单版本看起来更加通俗易懂,不像koa2官方库引用了npm官网上其他的包;当然也没有太多兼容处理,只是表达出来了核心内容