实现koa有这几个次要步骤:

  • 封装httpServer
  • 上下文context和requestresponse对象
  • 中间件函数 middleware
  • 错误处理

koa 外围源码外面包含这几个文件:

|-- koa    |-- lib        |-- application.js        |-- context.js        |-- request.js        |-- response.js

入口文件 application

application.js是外围入口文件,包含导出Koa类函数和外围代码的实现:

const http = require("http")class Koa {  constructor() {    // 上下文 context    this.ctx = Object.create({});    // 中间件函数    this.middleware = null  }  use(fn) {    // 将用户传入的函数绑定到中间件函数中    this.middleware = fn  }  handleRequest(req, res) {    let ctx = this.ctx;    // 给上下文增加 request 和 response 对象    ctx.req = req;    ctx.res = res;    // 执行中间件函数    this.middleware(ctx);    // 返回后果    ctx.body ? res.end(ctx.body) : res.end("Not Found")  }  listen() {    let server = http.createServer(this.handleRequest.bind(this))    server.listen(...arguments)  }}module.exports = Koa;

下面的代码曾经实现了httpServer服务的封装,有最根底的context上下文对象,绑定了requestresponse属性,能够在根目录下创立index.js文件测试:

let Koa = require("./koa/lib/application");let app = new Koa();app.use((ctx) => (ctx.body = ctx.req.url))app.listen(3000);

context、request和response

源码外面应用gettersetter属性,封装上下文contextreqres,也就是requestresponse对象。

request

创立request.js,退出上面代码:

let url = require('url');module.exports = {  get path() {    return url.parse(this.req.url,true).pathname  },  get query() {      return url.parse(this.req.url,true).query  }}

response

创立response.js,退出上面代码:

module.exports = {  _body: "",  get body() {    return this._body  },  set body(value) {    this.res.statusCode = 200    this._body = value  },}

context

创立context.js,退出上面代码:

let ctx = {}function defineGetter(property,key){    // 相当于去 property 上取值    ctx.__defineGetter__(key,function(){        return this[property][key]    });}function defineSetter(property,key){    // 相当于给 property 赋值    ctx.__defineSetter__(key, function (value) {      this[property][key]=value    })}defineGetter("request","path");defineGetter("request","query");defineGetter("response","body");defineSetter("response", "body")module.exports = ctx;

源码外面通过__defineSetter____defineGetter__requestresponse的属性挂载到了上下文context,接下啦批改application.js,引入contextresponserequest

const context = require("./context")const request = require("./request")const response = require("./response")...    constructor() {        ...        // Object.create避免用户间接批改对象,保障每次new Koa都是新的对象        this.context = Object.create(context)        this.request = Object.create(request)        this.response = Object.create(response)    }    createContext(req, res) {        let ctx = this.context        /*        * ctx.request.req\ctx.req\ctx.req        * ctx.response.res\ctx.res\ctx.res        */        ctx.request = this.request        ctx.response = this.response        ctx.request.req = ctx.req = req        ctx.response.res = ctx.res = res        return ctx    }    handleRequest(req, res) {        // 获取新的上下文        let ctx = this.createContext(req, res)        // 执行中间件函数        this.middleware(ctx);        ...    }

中间件 middleware

下面的middleware函数只绑定了一个办法,咱们晓得koa外面是能够绑定多个中间件函数,并且中间件函数蕴含上下文context和是否继续执行的next函数,因为koa2中应用的是async/await的形式,所以中间件函数返回的都会是一个Promise

middleware改成数组middlewares

  constructor() {    ...    this.middlewares = []    ...  }  use(fn){    //先将函数保留到中间件数组中    this.middlewares.push(fn)  }  ...

接下来创立compose办法解决中间件函数:

  compose(ctx, middlewares) {    // 以后函数执行指针    let exectIndex = -1    let dispatch = async function (index) {    // 避免同一个中间件函数呈现两个dispatch函数抛出异样      if (exectIndex >= index) return Promise.reject("mulit called next();")      exectIndex = index      // 全副执行实现返回Promise      if (index === middlewares.length) return Promise.resolve()      // 取出中间件函数解决      let middleware = middlewares[index]      // next函数持续取出下一个中间件函数执行      let next = () => dispatch(++index);      // 返回执行的中间件函数      return middleware(ctx, next)    }    return dispatch(0)  }

批改handleRequest函数如下:

  handleRequest(req, res) {    let ctx = this.createContext(req, res)    res.statusCode = 404    let p = this.compose(ctx, this.middlewares)    p.then(() => {      ctx.body ? res.end(ctx.body) : res.end("Not Found")    })  }

错误处理

koa 外面能够通过订阅error事件捕捉中间件函数运行过程中呈现的异样:

app.on("error",(error,ctx)=>{    ctx.res.end(error.toString())})

这理能够通过继承events对象,取得公布订阅的能力:

const EventEmiter = require("events")class Koa extends EventEmiter {    constructor() {        super()        ...    }    ...    handleRequest(req,res){        ...        p.then(() => {        ctx.body ? res.end(ctx.body) : res.end("Not Found")        }).catch(error=>{            // 铺货谬误后发送给error            this.emit("error", error, ctx)        })    }}

残缺的application.js代码:

const http = require("http")const Stream = require("stream")const EventEmiter = require("events")const context = require("./context")const request = require("./request")const response = require("./response")class Koa extends EventEmiter {  constructor() {    super()    this.middlewares = []    // Object.create避免用户间接批改对象,保障每次new Koa都是新的对象    this.context = Object.create(context)    this.request = Object.create(request)    this.response = Object.create(response)  }  use(fn) {    this.middlewares.push(fn)  }  compose(ctx, middlewares) {    let exectIndex = -1    let dispatch = async function (index) {      if (exectIndex >= index) return Promise.reject("mulit called next();")      exectIndex = index      if (index === middlewares.length) return Promise.resolve()      let middleware = middlewares[index]      return middleware(ctx, () => dispatch(++index))    }    return dispatch(0)  }  createContext(req, res) {    let ctx = this.context    /*    * ctx.request.req\ctx.req\ctx.req    * ctx.response.res\ctx.res\ctx.res    */    ctx.request = this.request    ctx.response = this.response    ctx.request.req = ctx.req = req    ctx.response.res = ctx.res = res    return ctx  }  handleRequest(req, res) {    let ctx = this.createContext(req, res)    res.statusCode = 404    let p = this.compose(ctx, this.middlewares)    p.then(() => {      if (ctx.body instanceof Stream) {        res.setHeader("Content-Type", "application/octet-stream")        res.setHeader("Content-Disposition", `attachment;filename=download`)        return ctx.body.pipe(res)      }      if (ctx.body) {        res.end(ctx.body)      } else {        res.end("Not Found")      }    }).catch((error) => {      this.emit("error", error, ctx)    })  }  listen() {    let server = http.createServer(this.handleRequest.bind(this))    server.listen(...arguments)  }}module.exports = Koa