閱讀這篇文章,你不需要聽過 koa 框架,你甚至不需要使用過 node,你只需簡單看懂 js。
相信每一個具有 web 開發(fā)經(jīng)驗的人都能輕松理解。
Node 主要用在開發(fā) Web 應(yīng)用,koa 是目前 node 里最流行的 web 框架。
在 Node 開啟一個 http 服務(wù)簡直易如反掌,官網(wǎng) demo。
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方法創(chuàng)建了一個http.Server的實例。 -
server監(jiān)聽3000端口。 - 我們傳入到
createServer里的函數(shù)實際是監(jiān)聽request事件的回調(diào),每當請求進來,監(jiān)聽函數(shù)就會執(zhí)行。 -
request事件的監(jiān)聽函數(shù),其函數(shù)接受兩個參數(shù),分別是req和res。其中req是一個可讀流,res是一個可寫流。我們通過req獲取http請求的所有信息,同時將數(shù)據(jù)寫入到res來對該請求作出響應(yīng)。
koa 應(yīng)用
koa 如何創(chuàng)建一個 server, 直接上個官網(wǎng)的例子
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);
中間件概念在編程中使用廣泛, 不管是前端還是后端, 在實際編程中或者框架設(shè)計都有使用到這種實用的模型。
基本上,Koa 所有的功能都是通過中間件實現(xiàn)的。
每個中間件默認接受兩個參數(shù),第一個參數(shù)是 Context 對象,第二個參數(shù)是 next 函數(shù)。只要調(diào)用 next 函數(shù),就可以把執(zhí)行權(quán)轉(zhuǎn)交給下一個中間件。
如果中間件內(nèi)部沒有調(diào)用 next 函數(shù),那么執(zhí)行權(quán)就不會傳遞下去。
多個中間件會形成一個棧結(jié)構(gòu)(middle stack),以“先進后出”(first-in-last-out)的順序執(zhí)行。整個過程就像,先是入棧,然后出棧的操作。
上面代碼的執(zhí)行順序是:
請求 ==> x-response-time 中間件 ==> logger 中間件 ==> response中間件 ==> logger 中間件 ==> response-time 中間件 ==> 響應(yīng)
理解 Koa 的中間件機制(源碼分析)
閱讀源碼,化繁為簡,我們看看 koa 的中間件系統(tǒng)是如何實現(xiàn)的。
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);
}
}
好了,精簡結(jié)束,一不小心,去枝末節(jié),最后只剩下不到 20 行代碼。
這就是框架的核心,簡化后的代碼非常清晰,有點不可思議,但核心就是這么簡單。
我們先分析以上代碼做了什么事。
我們定義了一個
middleware數(shù)組來存儲中間件。我們定一個了一個
use方法來注冊一個中間件。其實就是簡單的push到自身的mideware這個數(shù)組中。我們還使用了一個
compose方法,傳入middleware,應(yīng)該是做了一些處理,返回了一個可執(zhí)行的方法。
你一定對中間的 compose 方法很好奇,初此之外的代碼都容易理解,唯獨這個 compose 不太知道究竟做了什么。
其實,compose就是整個中間件框架的核心。
compose之外,代碼已經(jīng)很清楚的定義了
中間件的存儲
中間件的注冊
而 compose 方法做了最為重要的一件事
- 中間件的執(zhí)行
核心源碼 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);
}
}
};
}
我試圖去簡化一下這個方法,但方法本身已經(jīng)足夠簡潔。
代碼很簡潔。
通過 next()傳遞 實現(xiàn)中間件調(diào)用, 結(jié)合 Promise 采用 遞歸調(diào)用 的通知機制。
看圖

這種形式的控制流讓整個 Koa 框架中間件的訪問呈現(xiàn)出 自上而下的中間件流 + 自下而上的 response 數(shù)據(jù)流 的形式。
Koa 本身做的工作僅僅是定制了中間件的編寫規(guī)范,而不內(nèi)置任何中間件。一個 web request 會通過 Koa 的中間件棧,來動態(tài)完成 response 的處理。
koa 在中間件語法上面采用了 async+await 語法來生成 Promise 形式的程序控制流。
總結(jié)
koa 是非常精簡的框架, 其中的精粹思想就是洋蔥模型(中間件模型), koa 框架的中間件模型非常好用并且簡潔, 但是也有自身的缺陷, 一旦中間件數(shù)組過于龐大, 性能會有所下降,我們需要結(jié)合自身的情況與業(yè)務(wù)場景作出最合適的選擇.
