koa
-基於 Node.js
的下一代 web 開發框架。css
它最大的特色就是獨特的中間件流程控制,是一個典型的洋蔥模型。koa 和 koa2 中間件的思路是同樣的,可是實現方式有所區別,koa2 在 Node7.6 以後更是能夠直接用 async/await
來替代 generator
使用中間件,本文以最後一種狀況舉例。node
本文主要是對 compose 模塊的源碼解讀git
下面的圖是網上找的,很清晰的代表了一個請求是如何通過中間件最後生成響應的,這種模式中開發和使用中間件都是很是方便的。 咱們都知道在函數式編程的思想中,compose 是將多個函數合併成一個函數(g() + h() => g(h())),koa 中的 compose 則是將 koa/koa-router
各個中間件合併執行,結合 next()
就造成了下圖所示的洋蔥模型github
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('第一個中間件函數')
await next();
console.log('第一個中間件函數next以後!');
})
app.use(async (ctx, next) => {
console.log('第二個中間件函數')
await next();
console.log('第二個中間件函數next以後!');
})
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
複製代碼
執行命令 node demo1.js
web
執行結果以下所示:編程
爲何會是上面這種結果呢,咱們帶着這些疑問一塊兒去繼續往下看,看到最後確定會能理解。api
注意:在使用app.use
將給定的中間件添加到應用程序時,middlewar
(其實就是一個函數)接收兩個參數:ctx
和next
。其中next
也是一個函數。數組
compose 代碼以下,去掉註釋,代碼就 25 行,細讀確實是很精妙的代碼,雖然看着很短,但粗看幾層 return
,仍是會有點繞。bash
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
//傳入的 middleware 參數必須是數組
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
//middleware 數組的元素必須是函數
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise} 返回一個閉包函數,函數的返回是一個Promise 對象, 保持對 middleware 的引用。
* @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)
}
}
}
}
複製代碼
咱們首先去掉條件判斷,看下最裏面的實際返回閉包
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
複製代碼
fn = middleware[i]
也就是某一箇中間件,很顯然上述代碼遍歷中間件數組middleware
,依次拿到中間件fn,並執行:
fn(context, dispatch.bind(null, i + 1))
複製代碼
這裏能夠看到傳遞給中間件的兩個參數:context
和next
函數。 前面咱們提到過:在使用app.use
將定的中間件添加到應用程序時,中間件(其實就是一個函數)接收兩個參數:ctx
和next
。其中next
也是一個函數。 看到這裏是否是清楚了在註冊 middwleare
的時候爲何要有兩個參數了吧~
回到前面的問題,爲何咱們的demo
執行的結果會是上面,咱們看第一個中間件,
app.use(async (ctx, next) => {
console.log('第一個中間件函數')
await next();
console.log('第一個中間件函數next以後!');
})
複製代碼
帶入到代碼中,第一次執行 return dispatch(0)
, 這時第一個中間件被調用,繼續展開
dispatch(0)
展開Promise.resolve((async (ctx, next) => {
console.log('第一個中間件函數')
await next();
console.log('第一個中間件函數next以後');
})(context, dispatch.bind(null, i + 1)));
複製代碼
首先執行 console.log('第一個中間件函數')
沒啥毛病, 接下來執行 next()
方法,就跑到第二個中間件去了,因此沒有執行第二個 console.log()
app.use(async (ctx, next) => {
console.log('第二個中間件函數')
await next();
console.log('第二個中間件函數next以後!');
})
複製代碼
dispatch(1)
展開Promise.resolve(async (ctx, next) => Promise.resolve(async (ctx, next) => {
console.log('第一個中間件函數')
Promise.resolve((async (ctx, next) => {
console.log('第二個中間件函數')
await next();
console.log('第二個中間件函數next以後');
})(context, dispatch.bind(null, i + 1)));
console.log('第一個中間件函數next以後')
}))
複製代碼
因此執行 onsole.log('第二個中間件函數')
是否是就很清楚的看出來了。
在第二個中間件執行到await next()
時,一樣會輪轉到第三個中間件,接下若是有第四個中間件,第五個中間件,聰明的大家會發現,以此類推,直到最後一箇中間件。
看到這裏,咱們會不會很好奇 koa
是怎麼調用compose
的呢,等後面的文章再更新~
以上就是我關於 koa compose 的解讀和洋蔥模型的解析。但願對你們有所幫助,從代碼上咱們能夠看出,洋蔥模型也是有所缺陷的,一旦中間件過多,性能仍是會有必定的影響的,因此咱們須要結合本身的項目場景做出合適的選擇。
若是以上有問題,歡迎你們留言,一塊兒探討,謝謝!。