背景
Koa官方使用方法
const Koa = require('koa');
var Router = require('koa-router');
var router = new Router();
const app = new Koa();
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
});
//路由中间件
app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000...');
分析Koa.use()做了什么
Koa本质上是对node原生http模块的封装,并且引入了中间件机制,通过示例代码我们也知道,中间件就是一个个方法,通过这些方法使得整个web服务模块可以各司其职,有助于开发与调试。在整个http请求中,rquest与response需要经过一系列的中间过程处理然后返回给客户端使用。我们猜想在Koa的内部有这样的一个数组和一个use方法
let middleWare = []
function use(f){
middleWare.push(f)
}
在使用过一系列中间件之后,我们会得到这样一个middleWare
let middleWare = [f1,f2,f3,f4,f5,...]
举个列子说明
async function f1(ctx,next) {
console.log('执行到f1')
next()
}
async function f2(ctx,next) {
console.log('执行到f2')
next()
}
async function f3(ctx,next) {
console.log('执行到f3')
next()
}
async function f4(ctx,next) {
console.log('执行到f4')
next()
}
async function f5(ctx,next) {
console.log('执行到f5')
next()
}
以上是我们已经使用的5个中间件 我们怎么做到让这5个中间件以此执行呢?
看下面的代码
let next1 = async function (ctx) {
await f1(ctx,next2)
}
let next2 = async function (ctx) {
await f2(ctx,next3)
}
let next3 = async function (ctx) {
await f3(ctx,next4)
}
let next4 = async function (ctx) {
await f4(ctx,next5)
}
let next5 = async function (ctx) {
await f5()
}
next1(ctx) // next() === f1(f2(f3(f4(f5)))) ---> true
// ---> 执行到f1,执行到f2,执行到f3,执行到f4,执行到f5
由此看出,每个中间件的参数,是对于下一个中间中间件的引用函数。于是我们抽象一个方法出来来生成这些next,然后最后调用最后一个next就可以达到依次调用的效果
function createNext(middleWare,next){
return async function(){
await middleWare(next)
}
}
我们发现,createNext函数返回值又会成为下一个中间件的参数,next5被next4调用,next4又被next3调用......依次类推,很自然的就想到了数组的reduce方法 因此:
let composeMiddleWare = [f1,f2,f3,f4,f5].reduceRight((pre, cur, i, arr) => {
return createNext(cur, pre)
}, () => { })
composeMiddleWare() // ---> 执行到f1,执行到f2,执行到f3,执行到f4,执行到f5
Koa源码猜想
从上面的分析可得,koa源码中肯定有类似的实现
let middleWare = []
function use(f){
middleWare.push(f)
}
function createNext(middleWare,next,ctx){
return async function(){
await middleWare(ctx,next)
}
}
//组合中间件方法
let composeMiddleWare = (ctx) =>middleWare.reduceRight((pre, cur, i, arr) => {
return createNext(cur, pre,ctx)
}, () => { Promise.resolve() })
分析Koa中http模块原生api【createServer】的回调函数callback执行的时候携带了request与response对象,koa对其进行了封装生成了一个ctx对象,挂载了request,response上的部分属性,我们来猜猜callback里面做了什么?
http.createServer((req,res)=>callback(req,res))
function callback(req,res){
let composeMiddleWare = composeMiddleWare();//上面的composeMiddleWare
let ctx = {req,res} //有兴趣去看看源码,这里挂载全部属性
composeMiddleWare(ctx) //依次执行左右的中间件,并携带ctx
}
结语
Koa本身就是一个及其精简的框架,中间件的引入使它极其容易扩展,因此很受欢迎,也有很多大厂基于KOa的思想封装了自己的框架比如egg.js等等。先分析到这儿,下次写一篇Koa的简单实现。