Koa解决了原生http模块的三大问题

512 阅读2分钟
let http = require('http')
http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'})
  response.end('Hello World')
}).listen(8081, () => {
  console.log('Server running at http://127.0.0.1:8081/')
})

这是node的http核心模块使用方式,koa的出现解决了http模块的三个问题

原生http模块的问题

  • 代码编写方式的缺陷,所有逻辑都耦合在一个函数中
  • req和res的功能弱
  • http基于回调无法统一处理错误

koa给出的答案

中间件

  • 通过构建中间件将逻辑切片,使用 async 功能使得异步的代码有同步的书写方式

  • koa中间件有个经典的洋葱模型

洋葱模型

请求经过外层中间件到达内层中间件再返回外层,类似于DOM的事件捕获和冒泡。

每一层都是一个 async 函数 因此每一层都有等待下层中间件结束再继续执行的能力,当然你要确保在next()之前写上 await, 不然该层将不等待下层而继续执行。

既然每一层都是一个函数那么洋葱模型的构建就很简单了,函数套函数,将下层中间件传入上层中间件,由上层中间件调用。

use(callback) { // 注册中间件
    this.middlewares.push(callback)
}
compose(ctx) { // 构建洋葱模型
    // 将多个promise链接到一起 组成一个promise链 依次执行
    const dispatch = i => {
        if (i === this.middlewares.length) return Promise.resolve()
        let middleware = this.middlewares[i]
        // 将下层中间件通过延迟调用函数传入上层中间件
        return Promise.resolve(middleware(ctx, () => dispatch(i+1)))
    }
    return dispatch(0)
}

扩展req和res

Koa实例依赖于request、response和context这三个扩展对象,每个Koa实例都维护独立的扩展对象

request是对req的扩展,response是对res的扩展,context对象则代理了request和response的部分功能

同时每个请求也要维护独立的扩展对象

  createContext(req, res) { // 扩展req和res的功能构建ctx
    // 保证每次请求不共享上下文
    let ctx = Object.create(this.context)
    let _request = Object.create(this.request)
    let _response = Object.create(this.response)
    
    ctx.request = _request
    ctx.req = ctx.request.req = req
    ctx.response = _response
    ctx.res = ctx.response.res = res
    return ctx
  }

至于扩展了什么功能,就例如ctx.url

/*context*/
module.exports = {
  get url() {
      return this.request.url
  }
}
/*request*/
module.exports = {
    get url() {
        return this.req.url
    }
}

request作为第一层代理扩展了req,context作为第二层代理获取request和response上的扩展

基于事件模块统一处理错误

Koa继承了node中的Emitter(events模块)拥有发布订阅的功能,可以发布错误事件,并对其进行监听处理