上篇文章寫了如何閱讀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大神. 有問題能夠互相交流哈.