Koa原理及中间件模型实现
Koa带来了什么
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
相比Express,Koa使用了ES7的 async/await 语法实现异步,避免了回调地狱,十分优雅。Koa足够简洁,只提供基础功能,并不提供任何额外的功能。本身代码不到2000行,所有的功能都通过中间件实现。
短短几行代码就实现一个hello world应用。
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
源码结构
Koa极其精简,只有区区4个文件:
── lib
├── application.js
├── context.js
├── request.js
└── response.js
application.js
是Koa的入口,在这里主要做了这几件事:
- 创建HTTP服务
- 实现洋葱模型中间件机制
- 创建context,request,response
- 统一处理错误
context.js
主要功能就是代理了response对象和request对象的部分属性和方法。
request.js
是定义了request的原型,基于http模块的request抽象封装了一系列属性和方法。
response.js
与request类似,定义了 response 的原型,封装了一系列属性和方法。
Koa中间件机制
Koa最核心的功能就是它的中间件机制,中间件通过use方法注册,运行的时候从最外层开始执行,遇到 next 后加入下一个中间件,执行完毕后回到上一个中间件,这就是大家所熟知的洋葱模型。
中间件概念
Koa中间件其实就是一个函数,有两个参数:context和next,context就是Koa的context对象,next是一个函数,调用next就进入下一个中间件。
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('middleware1 start')
next && await next()
console.log('middleware1 end')
})
app.use(async (ctx, next) => {
console.log('middleware2 start')
next && await next()
console.log('middleware2 end')
})
app.use(async ctx => {
console.log('middleware3 start')
ctx.body = 'Hello World';
console.log('middleware3 end')
});
app.listen(3000);
当有请求进来后,上述的代码执行结果为:
middleware1 start
middleware2 start
middleware3 start
middleware3 end
middleware2 end
middleware1 end
同步实现
可以看到其实就是洋葱模型其实就是函数调用栈,查看Koa这部分代码,use
方法把中间件函数 push 进一个数组,之后用一个 componse
的函数组合起来,对于同步的中间件,可以简单实现一个 componse
函数。
const compose = (middlewares) => {
return (ctx) => {
const dispatch = (i) => {
if (!middlewares[i]) return
return middlewares[i](ctx, () => dispatch(i + 1))
}
return dispatch(0)
}
}
测试一下:
const fnFactory = (n) => (ctx, next) => {
console.log(`middleware${n} start`)
next && next()
console.log(`middleware${n} end`)
}
const middlewares = [fnFactory(1), fnFactory(2), fnFactory(3)]
const fnMiddleware = compose(middlewares)
fnMiddleware()
输出结果:
middleware1 start
middleware2 start
middleware3 start
middleware3 end
middleware2 end
middleware1 end
异步实现
Koa 的中间件模型不止能处理同步的中间件,还能处理异步的中间件,修改一下上面的代码,用 Promise
包装一下,支持异步的中间件模型就实现了。
const compose = (middlewares) => {
return (ctx) => {
const dispatch = (i) => {
if (!middlewares[i]) return Promise.resolve()
return Promise.resolve(middlewares[i](ctx, () => dispatch(i + 1)))
}
return dispatch(0)
}
}
测试一下:
const fnFactory = (n) => async (ctx, next) => {
console.log(`middleware${n} start`)
next && await next()
console.log(`middleware${n} end`)
}
const middlewares = [fnFactory(1), fnFactory(2), fnFactory(3)]
const fnMiddleware = compose(middlewares)
fnMiddleware()
输出结果:
middleware1 start
middleware2 start
middleware3 start
middleware3 end
middleware2 end
middleware1 end