1、从一个koa使用的例子开始
const app = new Koa()
app.use( async (ctx,next)=>{
console.log('a')
next()
})
app.use( async (ctx,next)=>{
console.log('b')
next()
})
app.on('error',err=>console.log(err))
app.listen(3000,()=>console.log('server starting at 3000'))
从这个例子我们大致可以知道需要设计哪些 api:
- 构造函数
Koa
,需要一个成员变量保存中间件函数 - 需要实现
Koa.prototype.use
方法收集中间件 - 需要实现
Koa.prototype.listen
方法启动服务器并顺序执行所有中间价,附带错误处理
2、简易实现
简易版实现的签名为 DIYKoa
2.1、 基本轮廓
const http = require('http')
const Event = require('events') // 实现错误监听
// 1、实现构造函数,支持无 new 调用
function DIYKoa(options){
if(!(this instanceof DIYKoa)){
return new DIYKoa(options)
}
Event.call(this)
this.options = options
this.middlewares = []
}
DIYKoa.prototype = EventEmitter.prototype
DIYKoa.prototype.constructor = DIYKoa
// 2、实现 use 方法收集中间件
DIYKoa.prototype.use = function (fn) {
if (typeof fn !== 'function') return
this.middlewares.push(fn)
return this
}
// 3、实现 listen 方法:启动服务器、处理中间件、错误处理
DIYKoa.prototype.listen = function (...args) {
const server = http.createServer(this.callback.bind(this)) // callback 是处理中间件依次调用的关键
server.listen(...args)
}
module.exports = DIYKoa
2.2、实现细节
DIYKoa.prototype.callback = function (req, res) {
const ctx = this.createContext(req, res) // 封装一下 ctx 对象 , ctx 将会作为中间件函数的 this
console.log(req.url);
const handleRequest = () => {
return res.end('hello world');
} // 处理中间件执行结果
const handleError = err => {
this.emit('error', err)
} // 触发 error 事件
const fn = this.compose()
fn(ctx).then(handleRequest).catch(handleError)
}
DIYKoa.prototype.compose = function () {
return async ctx => {
function createNext(middleware, oldNext) {
return async () => {
await middleware(ctx, oldNext);
}
}
let len = this.middlewares.length;
let next = async () => {
return Promise.resolve();
};
for (let i = len - 1; i >= 0; i--) {
let currentMiddleware = this.middlewares[i];
next = createNext(currentMiddleware, next);
}
await next();
};
}
DIYKoa.prototype.createContext =(req,res) => {
// 源码中对 context 做了很丰富的属性代理封装,但这并不是必须的,这里简要处理
const context = Object.create(null)
context.req = req
context.res = res
return context
}
2.3、验证
// 1、正常情况
const DIYKoa = require('./application.js')
const app = new DIYKoa()
app.use(async (ctx,next)=>{
console.log('a')
await next()
})
app.use(async (ctx,next)=>{
console.log('b')
await next()
})
app.on('error',err=>console.log(err))
app.listen(3000,()=>console.log('server starting at 3000'))
// 输出,注意这里有个坑,默认会请求两次,一次 / , 一次 /favicon.ico
a b
// 2、异常情况
const DIYKoa = require('./application.js')
const app = new DIYKoa()
app.use(async (ctx,next)=>{
console.log('a')
await next()
})
app.use(async (ctx,next)=>{
throw new Error('error')
await next()
})
app.on('error',err=>console.log(err))
app.listen(3000,()=>console.log('server starting at 3000'))
// 输出
a
Error error
参考
https://github.com/airuikun/blog/issues/2
https://juejin.im/post/5c7decbbe51d454a7c5e8474
网友评论