koa
是由express
原班人馬打造的一個更小、更富有表現力、更健壯的web
框架。node
在我眼中,koa
的確是比express
輕量的多,koa
給個人感受更像是一箇中間件框架,koa
只是一個基礎的架子,須要用到的相應的功能時,用相應的中間件來實現就好,諸如路由系統等。一個更好的點在於,express
是基於回調來處理,至於回調到底有多麼的很差,你們能夠自行搜索來看。koa1
基於的co
庫,因此koa1
利用Generator
來代替回調,而koa2
因爲node
對async/await
的支持,因此koa2
利用的是async/await
。關於async
以及co
庫等,你們能夠參考我以前寫過的一篇文章(理解async)。koa
能夠說是一個各類中間件的架子,下面就來看一下koa
對於中間件部分的實現:web
koa1
主要利用的是Generator
來實現,通常來講,koa1
的一箇中間件大概是長這個樣子的:數據庫
app.use(function *(next){ console.log(1); yield next; console.log(5); }); app.use(function *(next){ console.log(2); yield next; console.log(4); }); app.use(function *(){ console.log(3); });
這樣的輸出會是1, 2, 3, 4, 5
,koa
的中間件的實現主要依靠的是koa-compose
:express
function compose(middleware){ return function *(next){ if (!next) next = noop(); var i = middleware.length; // 組合中間件 while (i--) { next = middleware[i].call(this, next); } return yield *next; } } function *noop(){}
源碼很是的簡單,實現的功能就是將全部的中間件串聯起來,首先給倒數第一個中間件傳入一個noop
做爲其next
,再將這個整理後的倒數第一個中間做爲next
傳入倒數第二個中間件,最終獲得的next
就是整理後的第一個中間件。提及來比較複雜,畫圖來看:redux
實現的效果如同上圖,與redux
須要實現的目標相似,只要遇到了yield next
就去執行下一個中間件,利用co
庫很容易將這個流程串聯起來,下面來簡單模擬下,中間件完整的實現:segmentfault
const middlewares = []; const getTestMiddWare = (loggerA, loggerB) => { return function *(next) { console.log(loggerA); yield next; console.log(loggerB); } }; const mid1 = getTestMiddWare(1, 4), mid2 = getTestMiddWare(2, 3); const getData = new Promise((resolve, reject) => { setTimeout(() => resolve('數據已經取出'), 1000); }); function *response(next) { // 模擬異步讀取數據庫數據 const data = yield getData; console.log(data); } middlewares.push(mid1, mid2, response); // 簡單模擬co庫 function co(gen) { const ctx = this, args = Array.prototype.slice.call(arguments, 1); return new Promise((reslove, reject) => { if (typeof gen === 'function') gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== 'function') return resolve(gen); const baseHandle = handle => res => { let ret; try { ret = gen[handle](res); } catch(e) { reject(e); } next(ret); }; const onFulfilled = baseHandle('next'), onRejected = baseHandle('throw'); onFulfilled(); function next(ret) { if (ret.done) return reslove(ret.value); // 將yield的返回值轉換爲Proimse let value = null; if (typeof ret.value.then !== 'function') { value = co(ret.value); } else { value = ret.value; } if (value) return value.then(onFulfilled, onRejected); return onRejected(new TypeError('yield type error')); } }); } // 調用方式 const gen = compose(middlewares); co(gen);
隨着node
對於async/await
的支持,貌似不須要再借助於co
這種工具庫了,直接利用原生的就好,因而koa
也作出了改變,來看目前的koa-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() // 用Promise包裹中間件,方便await調用 try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }
koa-compose
利用了Promise
,koa2
的中間件的參數也有一個變爲了兩個,並且執行下一個的中間件利用的是await next()
,要達到與上面的示例代碼的相同效果,須要更改中間件的寫法:app
const middlewares = []; const getTestMiddWare = (loggerA, loggerB) => async (ctx, next) => { console.log(loggerA); await next(); console.log(loggerB); }; const mid1 = getTestMiddWare(1, 4), mid2 = getTestMiddWare(2, 3); const response = async () => { // 模擬異步讀取數據庫數據 const data = await getData(); console.log(data); }; const getData = () => new Promise((resolve, reject) => { setTimeout(() => resolve('數據已經取出'), 1000); }); middlewares.push(mid1, mid2); // 調用方式 compose(middlewares)(null, response);
能夠看到的是,koa1
與koa2
對於中間件的實現仍是有着不少的不一樣的,將koa1
的中間件直接拿到koa2
下面來使用確定是會出現錯誤的,如何兼容這兩個版本也成了一個問題,koa
團隊寫了一個包來是koa1
的中間件能夠用於koa2
中,叫作koa-convert
,先來看看這個包怎麼使用:框架
function *mid3(next) { console.log(2, 'koa1的中間件'); yield next; console.log(3, 'koa1的中間件'); } convert.compose(mid3)
來看下這個包實現的思路:koa
// 將參數轉爲數組,對每個koa1的中間件執行convert操做 convert.compose = function (arr) { if (!Array.isArray(arr)) { arr = Array.from(arguments) } return compose(arr.map(convert)) } // 關鍵在於convert的實現 const convert = mw => (ctx, next) => { // 藉助co庫,返回一個Promise,同時執行yield return co.call(ctx, mw.call(ctx, createGenerator(next))); }; function * createGenerator (next) { /* next爲koa-compomse中: function next () { return dispatch(i + 1) } */ return yield next() // 執行完koa1的中間件,又回到了利用await執行koa2中間件的正軌 }
我的感受koa-convert
的思路就是對Generator
封裝一層Promise
,使上一個中間件能夠利用await next()
的方式調用,對於Generator
的執行,利用co
庫,從而達到了兼容的目的。