引言
koa里的中间件就是一个函数而已。比如:
app.use(async (ctx, next) => {
console.log(3);
ctx.body = 'hello world';
await next();
console.log(4);
});
洋葱模型:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(5);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(3);
ctx.body = 'hello world';
});
app.listen(3001);
执行node index.js
,请求3001端口,输出是1~5。
作者:hans774882968以及hans774882968以及hans774882968
本文CSDN:https://blog.csdn.net/hans774882968/article/details/128843088
本文juejin:https://juejin.cn/post/7195249847044145208/
本文52pojie:https://www.52pojie.cn/thread-1740931-1-1.html
源码分析
use
函数是定义中间件的入口,传送门
use (fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!')
debug('use %s', fn._name || fn.name || '-')
this.middleware.push(fn)
return this
}
只是维护了一个middleware
数组。
接下来看看调用listen
的时候做什么。传送门
listen (...args) {
debug('listen')
const server = http.createServer(this.callback())
return server.listen(...args)
}
调用了callback
:
callback () {
const fn = this.compose(this.middleware)
if (!this.listenerCount('error')) this.on('error', this.onerror)
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
return handleRequest
}
那么需要看compose
。我们找到koa-compose
的源码,传送门
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
非常之短。看来node后端和前端一样是🐔⌨️🍚……
经过简单的几步追踪可以看到,中间件那些函数的执行位置就在compose
定义的dispatch
被执行时。compose
返回的function会传给handleRequest
,那么我们再看看handleRequest
:
/**
* Handle request in callback.
*
* @api private
*/
handleRequest (ctx, fnMiddleware) {
const res = ctx.res
res.statusCode = 404
const onerror = err => ctx.onerror(err)
const handleResponse = () => respond(ctx)
onFinished(res, onerror)
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
所以中间件是在请求处理的时候执行的,调用fnMiddleware
就是调用了compose
返回的function,也就调用了dispatch
。但是中间件最终由dispatch
函数调用,所以要搞清楚洋葱模型的实现,还是要看dispatch
函数。
dispatch(0)
表示第一个中间件即将执行,fn(context, dispatch.bind(null, i + 1))
这句话表示某个中间件正式执行。- 某个中间件调用
next
就是调用了dispatch.bind(null, i + 1)
,这样就产生了递归,下一个中间件即将开始执行。最后一个中间件结束后就会回到上一个中间件的next
结束之后的位置。 - 当
fnMiddleware(ctx)
执行完,所有中间件都已经执行完。而中间件执行完,请求已经执行完,之后handleRequest
还会做一些对返回值的处理,respond函数传送门 index
记录的是执行过的最靠后的中间件的下标。而dispatch
的参数i
是当前中间件的下标。如果i <= index
则表明next
在某个函数(中间件)执行了2次。这个i
参数的引入就是为了检测某个函数(中间件)执行2次next
的非法情况。- 从
handleRequest
调用fnMiddleware
的情况来看,next
参数是undefined
(可在node_modules
里自己加一个console.log
验证),所以if (i === middleware.length) fn = next
的作用,就是允许在最后一个中间件调用next
函数。
koa洋葱模型简化版实现
下面的代码为了简便,假设在listen
时立刻处理请求,不考虑异常处理等额外问题。扔浏览器控制台即可快速运行验证。
function compose(middleware) {
return (ctx) => {
function dispatch(i) {
if (i <= index) throw new Error('next() called multiple times');
index = i;
if (i >= middleware.length) return;
const fn = middleware[i];
return fn(ctx, () => dispatch(i + 1));
}
let index = -1;
return dispatch(0);
};
}
const app = {
middleware: [],
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
return this.middleware.push(fn);
},
handleRequest(fnMiddleware) {
console.log('handleRequest');
const ctx = {};
fnMiddleware(ctx);
},
listen() {
const fnMiddleware = compose(this.middleware);
this.handleRequest(fnMiddleware);
},
};
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(6, ctx.body);
});
app.use(async (ctx, next) => {
console.log(2);
await next();
console.log(5, ctx.body);
ctx.body += ' acmer';
});
app.use(async (ctx, next) => {
console.log(3);
ctx.body = 'hello world';
await next();
console.log(4);
});
app.listen(3000);
参考资料
- https://juejin.cn/post/6854573208348295182