本文分析了Koa框架的核心源碼,看懂了compose方法,也就看懂了 Koa。前端
Node 主要用在開發 Web 應用,koa 是目前 node 裏最流行的 web 框架。node
在 Node 開啓一個 http 服務簡直易如反掌,下面是官網 demo。web
const http = require("http");
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World\n");
});
const hostname = "127.0.0.1";
const port = 3000;
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
複製代碼
http
模塊,http
的createServer
方法建立了一個http.Server
的實例。server
監聽3000
端口。createServer
裏的函數實際是監聽request
事件的回調,每當請求進來,監聽函數就會執行。request
事件的監聽函數,其函數接受兩個參數,分別是req
和res
。其中 req 是一個可讀流,res 是一個可寫流。咱們經過 req 獲取 http 請求的全部信息,同時將數據寫入到 res 來對該請求做出響應。Koa 如何建立一個 server, 直接上個官網的例子編程
const Koa = require("koa");
const app = new Koa();
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set("X-Response-Time", `${ms}ms`);
});
// logger
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// response
app.use(async (ctx) => {
ctx.body = "Hello World";
});
app.listen(3000);
複製代碼
中間件概念在編程中使用普遍, 無論是前端仍是後端, 在實際編程或者框架設計中都有使用到這種實用的模型。後端
基本上,Koa 全部的功能都是經過中間件實現的。數組
每一箇中間件默認接受兩個參數,第一個參數是Context
對象,第二個參數是next
函數。只要調用next
函數,就能夠把執行權轉交給下一個中間件。app
若是中間件內部沒有調用next
函數,那麼執行權就不會傳遞下去。框架
多箇中間件會造成一個棧結構(middle stack),以「先進後出」(first-in-last-out)的順序執行。整個過程就像,先是入棧,而後出棧的操做。koa
上面代碼的執行順序是:
請求 -> x-response-time 中間件 -> logger 中間件 -> response 中間件 -> logger 中間件 -> response-time 中間件 -> 響應async
閱讀源碼,化繁爲簡,咱們看看 koa 的中間件系統是如何實現的。
class Application extends Emitter {
constructor() {
super();
this.middleware = [];
},
use(fn) {
this.middleware.push(fn);
return this;
},
callback() {
const fn = compose(this.middleware);
return function(req, res) {
return fn(ctx);
};
},
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
}
複製代碼
好了,精簡結束,一不當心,去枝末節,最後只剩下不到 20 行代碼。
這就是框架的核心,簡化後的代碼很是清晰,有點難以想象,但核心就是這麼簡單。
咱們先分析以上代碼作了什麼事。
middleware
數組來存儲中間件。use
方法來註冊一箇中間件。其實就是簡單的push
到自身的mideware
這個數組中。compose
方法,傳入middleware
,應該是作了一些處理,返回了一個可執行的方法。你必定對中間的compose
方法很好奇,初此以外的代碼都容易理解,惟獨這個compose
不太知道究竟作了什麼。其實,compose
就是整個中間件框架的核心。
compose
以外,代碼已經很清楚的定義了
而compose
方法作了最爲重要的一件事
compose,顧名思義,至關於把全部註冊的中間件組合,它規定了中間件的執行順序,返回一個「大」中間件,這個大中間件,就在 node 的 http 服務onRequest
事件的時候執行。
這是 compose
方法的源碼,代碼很簡潔。
function compose(middleware) {
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);
}
}
};
}
複製代碼
我試圖去簡化一下這個方法,但方法自己已經足夠簡潔。
經過compose
方法,能夠看出next
指的就是下一個中間件,也就是咱們所編寫的中間件方法的第二個參數,Koa 經過next()傳遞
實現中間件調用,結合 Promise
採用 遞歸調用 的通知機制,最終完成整個中間件棧的執行。
Koa 在中間件語法上採用了async
+await
語法來生成Promise
形式的程序控制流。
這種形式的控制流讓整個 Koa 框架中間件的訪問呈現出 自上而下的中間件流 + 自下而上的 response 數據流 的形式。
看懂了compose
方法,也就看懂了 Koa,你大概不肯相信如此簡單怎麼足以稱爲框架,但狀況就是這樣。
Koa 自己作的工做僅僅是定製了中間件的編寫規範,而不內置任何中間件。一個 webrequest
會經過 Koa 的中間件棧,來動態完成response
的處理。
koa 是很是精簡的框架,其中的精粹思想就是洋蔥模型(中間件模型),koa 框架的中間件模型很是好用而且簡潔,可是也有自身的缺陷,一旦中間件數組過於龐大,性能會有所降低,使用的時候要結合業務場景適當選擇。