上篇文章写了如何阅读Koa的源码, 粗略的过了一下Koa的源码, 可是做为一个没有得出一个具体的结论, 中间件的运行原理也不清楚, 这里咱们再仔细的过一遍Koa的源码.web
首先仍是先过一遍例子api
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
复制代码
起一个web服务, 来一个Hello World, 做为http模块的再封装, 咱们仍是慢慢来挖掘它是如何封装的吧(无关的代码我都会删掉).bash
首先是listen
:cookie
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
复制代码
http模块咱们都知道 无非是http.createServer(fn).listen(port)
, 其中fn带着req, res. 根据上面的封装咱们能够确定this.callback
确定是带着请求以及进行响应了. 那么再来看看this.callback
吧.app
callback() {
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
复制代码
果真callback
返回的函数是带着req, res的, 那我继续往下走看handleRequest
究竟经历了什么, ctx
大佬出现了, 咱们在用koa的时候全部请求响应都是挂在ctx上的, 看起来ctx是经过createContext
建立的, 那就继续看createContext
吧:koa
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req;
context.res = request.res = response.res = res;
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.cookies = new Cookies(req, res, {
keys: this.keys,
secure: request.secure
});
request.ip = request.ips[0] || req.socket.remoteAddress || '';
context.accept = request.accept = accepts(req);
context.state = {};
return context;
}
复制代码
createContext
比较简单, 就是把各类有用的没用的变量挂到context上, 代码也很简单, 可是由于涉及到request和response咱们须要简单看一下request.js和response.js:socket
module.exports = {
get header() {
return this.req.headers;
},
//..more items
}
复制代码
都是很简单获取变量没啥好说的, 那么回到前面callback部分, ctx建立好了而后调用并返回了this.handleReques
, 没啥好说的, 继续看呗:async
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
是咱们取出来的中间件, 而后咱们把ctx传到中间件里执行, 跟咱们的一般用法有点像了. 到这一步重点来了: 中间件函数
在探究中间件的原理以前, 不妨先来看看中间件是怎么用的, 来个简单的例子:ui
const Koa = require('koa')
const app = new Koa()
app.use(async function m1 (ctx, nex) {
console.log('m1')
await next()
console.log('m2 end')
})
app.use(async function m2 (ctx, nex) {
console.log('m2')
await next()
console.log('m2 end')
})
app.use(async function m3 (ctx, nex) {
console.log('m3')
ctx.body = 'Hello World'
})
复制代码
上面的结果很明确了, 可是咱们不妨来可视化一下:
m1: 输出m1
await1: m1你先暂停一下让m2先走
m1: ...
m2: 输出m2
await2: m2你也停一下让m3先走
m2: ...(委屈)
m3: 输出m3, 上面的注意啦要远路返回了
m2: 输出m2 end m1注意了我要返回啦
m1: 输出m1 end
respond: ctx.body是Hello world呢 就糊弄一下用户返回吧
复制代码
看到没, ctx.body不表明当即响应, 仅仅是一个咱们后面会用到的变量, 也就是说咱们的ctx过了一遍全部的中间件而后才会作出响应. 这里不提await神奇的暂停效果, 咱们就须要能够这么用就好了. 那么咱们这个中间件是怎么实现的呢, 来看compose.js:
function compose (middleware) {
/** * @param {Object} context * @return {Promise} * @api public */
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
index = i
let fn = middleware[i]
if (!fn) return Promise.resolve()
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
}
}
}
复制代码
看过我前一篇的能够知道这里其实就是一个递归. 可是跟connect的递归不同这里是Promise, 咱们都知道await 跟Promise搭配味道更佳嘛. 重点是这个next, 咱们调用了await next以后, 程序非得等这个Promise执行完不可, 咱们来简化一下中间件的模型:
Promise.resolve(async m1 () {
console.log(m1)
await Promise.resolve(async m2 () {
console.log(m2)
await Promise.resolve(async m3 () {
console.log(m3)
ctx.body = 'xxx'
})
console.log(m2 end)
})
console.log(m1 end)
})
复制代码
是否是这样一下就清楚了, 做为应用层的东西, 咱们不须要去考虑async/await到底是怎么实现的, 只须要了解它实现了什么样的效果.
仍是得佩服tj大神. 有问题能够互相交流哈.