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模块)拥有发布订阅的功能,可以发布错误事件,并对其进行监听处理