Koajs中間件之定義(一)
Koajs中間件之next(二)
Koajs中間件之context(三)
第一篇文章中咱們講過,「在Koa中,中間件是指連貫整個 Koa 應用程序,並共享資源的獨立插件」,注意兩個詞,「連貫」與「共享資源」,與上面的代碼一一對應,「連貫」對應「next」,「共享資源對應context」。
Koa 中經過 next 貫穿整個應用程序,下面分析一下 next 中作了什麼。javascript
Koa 類中的構造函數中初始化了一堆數據,其中兩個重要的,一個是「middleware」,另外一個是「context」。(非關鍵代碼使用...省略)java
constructor() { ... this.middleware = []; this.context = Object.create(context); ... }
全部的中間件在一個數組存放,當咱們調用「app.use」方法的時候會往數組中加入咱們自定義的中間價segmentfault
use(fn) { ... this.middleware.push(fn); return this; }
最後經過「koa-compose」來統一觸發中間件隊列api
callback() { const fn = compose(this.middleware); ... return (req, res) => { ... fn(ctx).then(() => respond(ctx)).catch(ctx.onerror); }; }
koa-compose 源碼只有短短几十行,關鍵代碼不到10行,直接貼上源碼數組
function compose (middleware) { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } /** * @param {Object} context * @return {Promise} * @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, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }
若是咱們須要使用 Koa 的洋蔥模型能夠直接調用 koa-componse 來達到目的bash
const koaCompose = require('koa-compose'); const middleware1 = (ctx, next) => { console.log('middleware1 >>>>>'); next(); console.log('middleware1 <<<<<'); } const middleware2 = (ctx, next) => { console.log('middleware2 >>>>>'); next(); console.log('middleware2 <<<<<'); } const middleware3 = (ctx, next) => { console.log('middleware3 >>>>>'); console.warn(ctx); next(); console.log('middleware3 <<<<<'); } const fn = koaCompose([middleware1, middleware2, middleware3]); fn({ a: 'a' }, (ctx) => { console.warn(ctx); console.warn('The last next use do =======<'); return ctx; }).then((ctx) => { console.warn(ctx); console.warn('end =====<'); });
輸出:app
middleware1 >>>>> middleware2 >>>>> middleware3 >>>>> { a: 'a' } { a: 'a' } The last next use do =======< middleware3 <<<<< middleware2 <<<<< middleware1 <<<<< undefined end =====<
爲了更好的分析代碼,去除 koa-componse 代碼中的各類非關鍵判斷及異步處理邏輯後,代碼以下koa
const compose = function (middlewares) { // 返回一個函數,提供兩個參數,一個是傳入的上下文,另外一個是全部中間件執行完畢後回調 return function(context, last) { let idx = -1; // 初始定義當前執行中間件下標未-1,即表示當前未執行任何中間件 return dispatch(0); // 手動觸發第1箇中間件 function dispatch(i) { idx = i; // 設置當前執行中間件下標 let fn = middlewares[i] || last; try { // 執行當前中間件的時候,給當前中間件參數中的next參數賦值爲一個函數,在這個函數中執行下一個中間件 if (fn) fn(context, function() { dispatch(i + 1); // 觸發下一個中間價,而且將中間件執行下標+1 }) } catch (err) { // 全部的中間件執行完畢,執行最後回調 last(context); } } } };
執行代碼:異步
const run = compose([middleware1, middleware2, middleware3]); run({ a: 'a' }, () => { console.warn('Middlewares do last =======<'); });
輸出:函數
middleware1 >>>>> middleware2 >>>>> middleware3 >>>>> Middlewares do last =======< middleware3 <<<<< middleware2 <<<<< middleware1 <<<<<