摘要:如果驗(yàn)證沒出現(xiàn)問題,就注冊這個(gè)中間件并放到中間件數(shù)組中。但如果不執(zhí)行,中間件的處理也會(huì)終止。整理下流程默認(rèn)會(huì)執(zhí)行中間件數(shù)組中的第一個(gè),也就是代碼中的,第一個(gè)中間件通過返回的是第二個(gè)中間件的執(zhí)行。
介紹
如果你使用過redux或者nodejs,那么你對“中間件”這個(gè)詞一定不會(huì)感到陌生,如果沒用過這些也沒關(guān)系,也可以通過這個(gè)來了解javascript中的事件流程。
一個(gè)例子有一類人,非常的懶(比如說我),只有三種行為動(dòng)作,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í)行結(jié)果如下圖:
不管什么,先睡2S
然后做個(gè)介紹,吃東西,睡5S
醒來,吃
但是javascript只有一個(gè)線程,也并沒有像php的sleep的那種方法。實(shí)現(xiàn)的思路就是eat、sleep、sleepFirst這些事件放在任務(wù)列中,通過next去依次執(zhí)行方法。我還是希望在看源碼前先手動(dòng)實(shí)現(xiàn)一下試試看,其實(shí)這就是個(gè)lazyMan的實(shí)現(xiàn)。
下面是我的實(shí)現(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,你會(huì)發(fā)現(xiàn),這種寫法似乎有點(diǎn)熟悉的感覺,我們來看一下一個(gè)koa2(一個(gè)node的框架)項(xiàng)目的主文件:
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)結(jié)構(gòu)有一點(diǎn)像?
koa中的中間件廢話不多說,直接看源碼...
app.use就是用來注冊中間件的,我們先看use的實(shí)現(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ù),首先肯定要判斷是否是個(gè)函數(shù),如果不是,拋出錯(cuò)誤,其次是判斷fn是否是一個(gè)GeneratorFunction,我用的是koa2,koa2中用async、await來替代koa1中的generator,如果判斷是生成器函數(shù),證明使用或者書寫的中間件為koa1的,koa2中提供了庫koa-convert來幫你把koa1中的中間件轉(zhuǎn)換為koa2中的中間件,這里如果判斷出是koa1的中間件會(huì)給你提醒,這里會(huì)主動(dòng)幫你轉(zhuǎn)換,就是代碼中的convert方法。如果驗(yàn)證沒出現(xiàn)問題,就注冊這個(gè)中間件并放到中間件數(shù)組中。
這里我們只看到了把中間件加到數(shù)組中,然后就沒有做其他處理了。
我們再看koa2中listen
listen(...args) { debug("listen"); const server = http.createServer(this.callback()); return server.listen(...args); }
這里只是啟動(dòng)了個(gè)server,然后傳進(jìn)了一個(gè)回調(diào)函數(shù)的結(jié)果,我們看原生啟動(dòng)一個(gè)server大概是什么樣的:
https.createServer(options, function (req, res) { res.writeHead(200); res.end("hello world "); }).listen(3000);
原生的回調(diào)函數(shù)接受兩個(gè)參數(shù),一個(gè)是request一個(gè)是response,我們再去看koa2中這個(gè)回調(diào)函數(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; }
這里有一個(gè)const fn = compose(this.middleware);,compose這種不知道大家用的多不多,compose是函數(shù)式編程中使用比較多的東西,這里將多個(gè)中間件組合起來。
我們?nèi)タ碿ompose的實(shí)現(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ù)組,這個(gè)不用多說,for...of是ES6中的新特性,這里不做說明,需要注意的是,數(shù)組和Set集合默認(rèn)的迭代器是values()方法,Map默認(rèn)的是entries()方法。
這里的dispatch和next一樣是所有的中間件的核心,dispatch的參數(shù)i其實(shí)也就是對應(yīng)中間件的下標(biāo),,在第一次調(diào)用的時(shí)候傳入了參數(shù)0,如果中間件存在返回Promise:
return Promise.resolve(fn(context, function next () { return dispatch(i + 1) }))
我們lazyMan鏈?zhǔn)秸{(diào)用時(shí)不斷的shift()取出下一個(gè)要執(zhí)行的事件函數(shù),koa2里采用的是通過數(shù)組下標(biāo)的方式找到下一個(gè)中間件,這里是用Promise.resolve包起來就達(dá)到了每一個(gè)中間件await next()返回的結(jié)果都剛好是下一個(gè)中間件的執(zhí)行。不難看出此處dispatch是個(gè)遞歸調(diào)用,多個(gè)中間件會(huì)形成一個(gè)棧結(jié)構(gòu)。其中i的值總是比上一次傳進(jìn)來的大,正常執(zhí)行index的值永遠(yuǎn)小于i,但只要在同一個(gè)中間件中next執(zhí)行兩次以上,index的值就會(huì)等于i,同時(shí)會(huì)拋出錯(cuò)誤。但如果不執(zhí)行next,中間件的處理也會(huì)終止。
整理下流程:
compose(this.middleware)(ctx)默認(rèn)會(huì)執(zhí)行中間件數(shù)組中的第一個(gè),也就是代碼中的dispatch(0),第一個(gè)中間件通過await next()返回的是第二個(gè)中間件的執(zhí)行。
然后第二個(gè)中間件中執(zhí)行await next(),然后返回第三個(gè)...以此類推
中間件全部處理結(jié)束以后,剩下的就是通過中間件中不斷傳遞的context來對請求作處理了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85225.html
摘要:一個(gè)標(biāo)準(zhǔn)性的事件就是年的橫空出世。引擎快速處理能力和異步編程風(fēng)格,讓開發(fā)者從多線程中解脫了出來。其次,通過異步編程范式將其高并發(fā)的能力發(fā)揮的淋漓盡致。它也僅僅是一個(gè)處理請求并作出響應(yīng)的函數(shù),并無任何特殊之處。 showImg(https://segmentfault.com/img/remote/1460000010819116); 在正式學(xué)習(xí) Express 內(nèi)容之前,我們有必要從大...
摘要:三中間件實(shí)現(xiàn)原理首先需要明確是中間件并不是中的概念,它只是和框架衍生的概念。中間件的執(zhí)行流程主要由與函數(shù)決定依次取出中間件終止條件路由匹配規(guī)則函數(shù)中使用閉包函數(shù)來檢測是否與當(dāng)前路由相匹配,匹配則執(zhí)行該上的中間件函數(shù),否則繼續(xù)檢查下一個(gè)。 Koa作為下一代Web開發(fā)框架,不僅讓我們體驗(yàn)到了async/await語法帶來同步方式書寫異步代碼的酸爽,而且本身簡潔的特點(diǎn),更加利于開發(fā)者結(jié)合業(yè)務(wù)...
摘要:調(diào)用函數(shù)執(zhí)行下一個(gè)中間件函數(shù)。然后,該中間件調(diào)用函數(shù)檢查文件是否存在。為了代碼更加清晰,你也可以將代碼改寫為另外,這里在調(diào)用函數(shù)是使用的是作為輸出選項(xiàng)。事實(shí)上,中間件有兩種類型。 原生 Node 的單一請求處理函數(shù),隨著功能的擴(kuò)張勢必會(huì)變的越來越難以維護(hù)。而 Express 框架則可以通過中間件的方式按照模塊和功能對處理函數(shù)進(jìn)行切割處理。這樣拆分后的模塊不僅邏輯清晰,更重要的是對后期維...
摘要:原文博客地址,歡迎學(xué)習(xí)交流點(diǎn)擊預(yù)覽讀了下的源碼,寫的相當(dāng)?shù)木啠龅教幚碇虚g件執(zhí)行的模塊決定學(xué)習(xí)一下這個(gè)模塊的源碼。當(dāng)在下游沒有更多的中間件執(zhí)行后,堆棧將展開并且每個(gè)中間件恢復(fù)執(zhí)行其上游行為。 原文博客地址,歡迎學(xué)習(xí)交流:點(diǎn)擊預(yù)覽 讀了下Koa的源碼,寫的相當(dāng)?shù)木啠龅教幚碇虚g件執(zhí)行的模塊koa-Compose,決定學(xué)習(xí)一下這個(gè)模塊的源碼。 閱讀本文可以學(xué)到: Koa中間件的加載...
摘要:指定需要處理的路由回調(diào)函數(shù),即請求此路由的處理函數(shù),它可以接收兩個(gè)參數(shù)三個(gè)參數(shù),四個(gè)參數(shù)。如果匹配到自定義的路由,立即執(zhí)行回調(diào)函數(shù),如果處理函數(shù)中沒有則不再往下執(zhí)行,如果執(zhí)行了會(huì)繼續(xù)向下匹配。 簡介 Node.js? is a JavaScript runtime built on Chromes V8 JavaScript engine. Node.js uses an event-...
閱讀 2211·2021-09-02 15:11
閱讀 1530·2019-08-30 15:43
閱讀 2083·2019-08-29 13:48
閱讀 2803·2019-08-26 13:55
閱讀 2110·2019-08-23 15:09
閱讀 2905·2019-08-23 14:40
閱讀 3438·2019-08-23 14:23
閱讀 2646·2019-08-23 14:20