摘要:現在我們從實現一個簡易的方法開始探索其中的機制。其中內部的可以將上一個的返回值傳遞給外部。一言以蔽之實現了遞歸調用的方法。當執行到的中間件沒有時并且返回的為時逆序執行。
本文發布在github.com/ssssyoki,歡迎star,issues共同交流。
Koa是基于Node.js的下一代web開發框架,相比Express更輕,源碼只有幾百行。與傳統的中間件不同,在Koa 1.x中采用了generator實現中間件,這需要開發者熟悉ES6中的generator,Promise相關知識。
在Koa官方文檔示例代碼中,采用yield next為跳轉信號,然后會逆序執行中間件剩下的代碼邏輯。這其中的邏輯非常有趣,本文將對其進行簡要的分析。
Section A:Koa的中間件跑在co模塊下,而co可以將異步“變為”同步,從而實現用同步的方法寫異步代碼,避免了Node.js大量的回調嵌套。現在我們從實現一個簡易的co方法開始探索其中的機制。
function co(generator){ let g = generator(); let next = function(data){ let result = g.next(data); if(result.done){ return ; }; if(result.value instanceof Promise){ result.value.then(function(d){ next(d); },function(err){ next(err); }); }else{ next(); }; }; next(); };
首先需要了解generator相關知識,接下來我們分析這段代碼:
首先定義一個參數為generator的co函數,當傳入generator后(即app.use(function *(){...}))定義next方法實現對generator(可以理解為狀態機)的狀態遍歷,由于每次遍歷器指向新的yield,返回結構如{value:"Promise","done":"true/false"}的值,當done的值為false時遍歷狀態完畢并返回,若為true則繼續遍歷。其中內部的g.next(data)可以將上一個yield的返回值傳遞給外部。同時,若generator中含有多個yield且遍歷未完成(即result.value是Promise對象 && result.done === false),resolve()所傳遞的數據可以在接下來then()方法中直接使用,即遞歸調用,直到result.done === true遍歷結束并退出。
這里可能存在一個疑惑,在第一次調用next()方法時data為undefined,那是否會導致error產生呢?其實V8引擎在執行時,會自動忽略第一次調用next()時的參數,所以只有從第二次使用next()方法時參數才是有效的。
一言以蔽之,co實現了Promise遞歸調用generator的next方法。
理解了co的運行原理后,再來理解middleware的機制就容易多了。
middleware實現了所謂“逆序”執行,其實就是每次調用use()方法時,將generator存入數組(記為s)中保存。在執行的時候先定義一個執行索引(記為index)和跳轉標記(記為turn,也就是yield next中的next),再定義一個保存generator函數對象的數組(記為gs)。然后獲取當前中間件generator,接著獲取該generator的函數對象,將函數對象放在gs數組內保存,再執行generator的next()方法。
執行開始后,根據返回的value進行不同的處理,如果是標記turn(即執行到了yield next),說明該跳到下一個中間件了,此時令index++,然后從數組g中獲取下一個中間件重復上一個中間件的執行流程。
當執行到的中間件沒有yield時,并且返回的done為true時,逆序執行。從此前用于保存generator函數對象的gs數組中取出上一個generator對象,然后執行generator的next()方法,直到全部結束。
我們打開Koa的application.js文件:
/** * Use the given middleware "fn". * * @param {GeneratorFunction} fn * @return {Application} self * @api public */ app.use = function(fn){ if (!this.experimental) { // es7 async functions are not allowed, // so we have to make sure that "fn" is a generator function assert(fn && "GeneratorFunction" == fn.constructor.name, "app.use() requires a generator function"); } debug("use %s", fn._name || fn.name || "-"); this.middleware.push(fn); return this; };
顯而易見,app.use()方法就是將generator傳入this.middleware數組中。其他部分的邏輯源碼注釋非常清晰,不再贅述。
我們再打開Koa-compose模塊的index.js文件:
/** * Compose `middleware` returning * a fully valid middleware comprised * of all those which are passed. * * @param {Array} middleware * @return {Function} * @api public */ 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; } }
其中最關鍵的就是while語句。將之前app.use()傳入并存儲在middleware中的generator逆序取出并執行,將每個generator執行后的結果(即generator() === iterator)作為參數傳入下一個(按數組的順序則為前一個)generator中,在最后一個generator(數組第一個)執行后得出的next變量(即第一個generator的iterator),執行yield *next(即執行第一個generator的iterator)將全部generator像鏈表般串聯起來。根據yield *的特性,yield *next將依次執行所有套用的next(類似遞歸),從而形成所謂“正序執行再逆序執行”的流程。
從co到compose,代碼只有短短幾十行,但組合在一起卻非常精巧奇妙,值得細細品味。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91368.html
摘要:代碼根據服務提供者和服務調用方法名,獲取。代碼根據服務提供者配置的最大并發度,創建該服務該方法對應的信號量對象。總結是控制消費端對單個服務提供者單個服務允許調用的最大并發度。 本文將詳細分析< dubbo:service executes=/>與< dubbo:reference actives = />的實現機制,深入探...
閱讀 2000·2023-04-25 16:53
閱讀 1442·2021-10-13 09:39
閱讀 606·2021-09-08 09:35
閱讀 1639·2019-08-30 13:03
閱讀 2121·2019-08-30 11:06
閱讀 1831·2019-08-30 10:59
閱讀 3188·2019-08-29 17:00
閱讀 2288·2019-08-23 17:55