摘要:如果驗證沒出現(xiàn)問題,就注冊這個中間件并放到中間件數(shù)組中。但如果不執(zhí)行,中間件的處理也會終止。整理下流程默認會執(zhí)行中間件數(shù)組中的第一個,也就是代碼中的,第一個中間件通過返回的是第二個中間件的執(zhí)行。
介紹
如果你使用過redux或者nodejs,那么你對“中間件”這個詞一定不會感到陌生,如果沒用過這些也沒關系,也可以通過這個來了解javascript中的事件流程。
一個例子有一類人,非常的懶(比如說我),只有三種行為動作,sleep,eat,sleepFirst,偽代碼就是:
var wang = new LazyMan("王大錘"); wang.eat("蘋果").eat("香蕉").sleep(5).eat("葡糖").eat("橘子").sleepFirst(2); //等同于以下的代碼 const wang = new LazyMan("王大錘"); wang.eat("蘋果"); wang.eat("香蕉"); wang.sleep(5); wang.eat("葡糖"); wang.eat("橘子"); wang.sleepFirst(2);
執(zhí)行結果如下圖:
不管什么,先睡2S
然后做個介紹,吃東西,睡5S
醒來,吃
但是javascript只有一個線程,也并沒有像php的sleep的那種方法。實現(xiàn)的思路就是eat、sleep、sleepFirst這些事件放在任務列中,通過next去依次執(zhí)行方法。我還是希望在看源碼前先手動實現(xiàn)一下試試看,其實這就是個lazyMan的實現(xiàn)。
下面是我的實現(xiàn)方式:
class lazyMan{ constructor(name) { this.tasks = []; const first = () => { console.log(`my name is ${name}`); this.next(); } this.tasks.push(first); setTimeout(()=>this.next(), 0); } next() { const task = this.tasks.shift(); task && task(); } eat(food) { const eat = () => { console.log(`eat ${food}`); this.next(); }; this.tasks.push(eat); return this; } sleep(time) { const newTime = time * 1000; const sleep = () => { console.log(`sleep ${time}s!`); setTimeout(() => { this.next(); }, newTime); }; this.tasks.push(sleep); return this; } sleepFirst(time) { const newTime = time * 1000; const sleepzFirst = () => { console.log(`sleep ${time}s first!`); setTimeout(() => { this.next(); }, newTime); }; this.tasks.unshift(sleepzFirst); return this; } } const aLazy = new lazyMan("王大錘"); aLazy.eat("蘋果").eat("香蕉").sleep(5).eat("葡萄").eat("橘子").sleepFirst(2)
我們上面說過
wang.eat("蘋果").eat("香蕉").sleep(5).eat("葡糖").eat("橘子").sleepFirst(2); //等同于以下的代碼 wang.eat("蘋果"); wang.eat("香蕉"); wang.sleep(5); wang.eat("葡糖"); wang.eat("橘子"); wang.sleepFirst(2);
如果你使用過過node,你會發(fā)現(xiàn),這種寫法似乎有點熟悉的感覺,我們來看一下一個koa2(一個node的框架)項目的主文件:
const Koa = require("koa"); const bodyParser = require("koa-bodyparser"); const cors = require("koa-cors2"); const routers = require("./src/routers/index") const app = new Koa(); app.use(cors()); app.use(bodyParser()); app.use(routers.routes()).use(routers.allowedMethods()) app.listen(3000);
有沒有發(fā)現(xiàn)結構有一點像?
koa中的中間件廢話不多說,直接看源碼...
app.use就是用來注冊中間件的,我們先看use的實現(xiàn):
use(fn) { if (typeof fn !== "function") throw new TypeError("middleware must be a function!"); if (isGeneratorFunction(fn)) { deprecate("Support for generators will be removed in v3. " + "See the documentation for examples of how to convert old middleware " + "https://github.com/koajs/koa/blob/master/docs/migration.md"); fn = convert(fn); } debug("use %s", fn._name || fn.name || "-"); this.middleware.push(fn); return this; }
先解釋一下里面做了什么處理,fn就是傳入的函數(shù),首先肯定要判斷是否是個函數(shù),如果不是,拋出錯誤,其次是判斷fn是否是一個GeneratorFunction,我用的是koa2,koa2中用async、await來替代koa1中的generator,如果判斷是生成器函數(shù),證明使用或者書寫的中間件為koa1的,koa2中提供了庫koa-convert來幫你把koa1中的中間件轉換為koa2中的中間件,這里如果判斷出是koa1的中間件會給你提醒,這里會主動幫你轉換,就是代碼中的convert方法。如果驗證沒出現(xiàn)問題,就注冊這個中間件并放到中間件數(shù)組中。
這里我們只看到了把中間件加到數(shù)組中,然后就沒有做其他處理了。
我們再看koa2中listen
listen(...args) { debug("listen"); const server = http.createServer(this.callback()); return server.listen(...args); }
這里只是啟動了個server,然后傳進了一個回調函數(shù)的結果,我們看原生啟動一個server大概是什么樣的:
https.createServer(options, function (req, res) { res.writeHead(200); res.end("hello world "); }).listen(3000);
原生的回調函數(shù)接受兩個參數(shù),一個是request一個是response,我們再去看koa2中這個回調函數(shù)的代碼:
callback() { const fn = compose(this.middleware); if (!this.listeners("error").length) this.on("error", this.onerror); const handleRequest = (req, res) => { res.statusCode = 404; const ctx = this.createContext(req, res); const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); onFinished(res, onerror); return fn(ctx).then(handleResponse).catch(onerror); }; return handleRequest; }
這里有一個const fn = compose(this.middleware);,compose這種不知道大家用的多不多,compose是函數(shù)式編程中使用比較多的東西,這里將多個中間件組合起來。
我們去看compose的實現(xiàn):
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) } } } }
首先判斷是否是中間件數(shù)組,這個不用多說,for...of是ES6中的新特性,這里不做說明,需要注意的是,數(shù)組和Set集合默認的迭代器是values()方法,Map默認的是entries()方法。
這里的dispatch和next一樣是所有的中間件的核心,dispatch的參數(shù)i其實也就是對應中間件的下標,,在第一次調用的時候傳入了參數(shù)0,如果中間件存在返回Promise:
return Promise.resolve(fn(context, function next () { return dispatch(i + 1) }))
我們lazyMan鏈式調用時不斷的shift()取出下一個要執(zhí)行的事件函數(shù),koa2里采用的是通過數(shù)組下標的方式找到下一個中間件,這里是用Promise.resolve包起來就達到了每一個中間件await next()返回的結果都剛好是下一個中間件的執(zhí)行。不難看出此處dispatch是個遞歸調用,多個中間件會形成一個棧結構。其中i的值總是比上一次傳進來的大,正常執(zhí)行index的值永遠小于i,但只要在同一個中間件中next執(zhí)行兩次以上,index的值就會等于i,同時會拋出錯誤。但如果不執(zhí)行next,中間件的處理也會終止。
整理下流程:
compose(this.middleware)(ctx)默認會執(zhí)行中間件數(shù)組中的第一個,也就是代碼中的dispatch(0),第一個中間件通過await next()返回的是第二個中間件的執(zhí)行。
然后第二個中間件中執(zhí)行await next(),然后返回第三個...以此類推
中間件全部處理結束以后,剩下的就是通過中間件中不斷傳遞的context來對請求作處理了。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85225.html
摘要:一個標準性的事件就是年的橫空出世。引擎快速處理能力和異步編程風格,讓開發(fā)者從多線程中解脫了出來。其次,通過異步編程范式將其高并發(fā)的能力發(fā)揮的淋漓盡致。它也僅僅是一個處理請求并作出響應的函數(shù),并無任何特殊之處。 showImg(https://segmentfault.com/img/remote/1460000010819116); 在正式學習 Express 內容之前,我們有必要從大...
摘要:三中間件實現(xiàn)原理首先需要明確是中間件并不是中的概念,它只是和框架衍生的概念。中間件的執(zhí)行流程主要由與函數(shù)決定依次取出中間件終止條件路由匹配規(guī)則函數(shù)中使用閉包函數(shù)來檢測是否與當前路由相匹配,匹配則執(zhí)行該上的中間件函數(shù),否則繼續(xù)檢查下一個。 Koa作為下一代Web開發(fā)框架,不僅讓我們體驗到了async/await語法帶來同步方式書寫異步代碼的酸爽,而且本身簡潔的特點,更加利于開發(fā)者結合業(yè)務...
摘要:調用函數(shù)執(zhí)行下一個中間件函數(shù)。然后,該中間件調用函數(shù)檢查文件是否存在。為了代碼更加清晰,你也可以將代碼改寫為另外,這里在調用函數(shù)是使用的是作為輸出選項。事實上,中間件有兩種類型。 原生 Node 的單一請求處理函數(shù),隨著功能的擴張勢必會變的越來越難以維護。而 Express 框架則可以通過中間件的方式按照模塊和功能對處理函數(shù)進行切割處理。這樣拆分后的模塊不僅邏輯清晰,更重要的是對后期維...
摘要:原文博客地址,歡迎學習交流點擊預覽讀了下的源碼,寫的相當?shù)木啠龅教幚碇虚g件執(zhí)行的模塊決定學習一下這個模塊的源碼。當在下游沒有更多的中間件執(zhí)行后,堆棧將展開并且每個中間件恢復執(zhí)行其上游行為。 原文博客地址,歡迎學習交流:點擊預覽 讀了下Koa的源碼,寫的相當?shù)木啠龅教幚碇虚g件執(zhí)行的模塊koa-Compose,決定學習一下這個模塊的源碼。 閱讀本文可以學到: Koa中間件的加載...
摘要:指定需要處理的路由回調函數(shù),即請求此路由的處理函數(shù),它可以接收兩個參數(shù)三個參數(shù),四個參數(shù)。如果匹配到自定義的路由,立即執(zhí)行回調函數(shù),如果處理函數(shù)中沒有則不再往下執(zhí)行,如果執(zhí)行了會繼續(xù)向下匹配。 簡介 Node.js? is a JavaScript runtime built on Chromes V8 JavaScript engine. Node.js uses an event-...
閱讀 2204·2021-09-02 15:11
閱讀 1511·2019-08-30 15:43
閱讀 2081·2019-08-29 13:48
閱讀 2798·2019-08-26 13:55
閱讀 2107·2019-08-23 15:09
閱讀 2903·2019-08-23 14:40
閱讀 3431·2019-08-23 14:23
閱讀 2640·2019-08-23 14:20