成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

通過實(shí)例分析javascript中的“中間件”

zhangyucha0 / 3113人閱讀

摘要:如果驗(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

相關(guān)文章

  • Express 實(shí)戰(zhàn)(一):概覽

    摘要:一個(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)容之前,我們有必要從大...

    zhaochunqi 評論0 收藏0
  • 玩轉(zhuǎn)Koa -- 核心原理分析

    摘要:三中間件實(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ù)...

    jsbintask 評論0 收藏0
  • Express 實(shí)戰(zhàn)(四):中間件

    摘要:調(diào)用函數(shù)執(zhí)行下一個(gè)中間件函數(shù)。然后,該中間件調(diào)用函數(shù)檢查文件是否存在。為了代碼更加清晰,你也可以將代碼改寫為另外,這里在調(diào)用函數(shù)是使用的是作為輸出選項(xiàng)。事實(shí)上,中間件有兩種類型。 原生 Node 的單一請求處理函數(shù),隨著功能的擴(kuò)張勢必會(huì)變的越來越難以維護(hù)。而 Express 框架則可以通過中間件的方式按照模塊和功能對處理函數(shù)進(jìn)行切割處理。這樣拆分后的模塊不僅邏輯清晰,更重要的是對后期維...

    mochixuan 評論0 收藏0
  • 中間件執(zhí)行模塊koa-Compose源碼分析

    摘要:原文博客地址,歡迎學(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中間件的加載...

    imtianx 評論0 收藏0
  • Express 搭建服務(wù)器

    摘要:指定需要處理的路由回調(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-...

    CrazyCodes 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<