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

資訊專欄INFORMATION COLUMN

你知道koa中間件執(zhí)行原理嗎

kumfo / 562人閱讀

摘要:拿到下一個(gè)中間件后把他交給去處理當(dāng)中間件執(zhí)行結(jié)束了,就把的狀態(tài)設(shè)置為成功。

前言

原文地址

最近幾天花了比較長的時(shí)間在koa(1)的源碼分析上面,初次看的時(shí)候,被中間件執(zhí)行那段整的暈乎乎的,完全不知道所以,再次看,好像明白了些什么,再反復(fù)看,我去,簡直神了,簡直淚流滿面,簡直喪心病狂?。。?!

用在前面

下面的例子會在控制臺中打印出一些信息(具體打印出什么?可以猜猜?),然后返回hello world。

let koa = require("koa")
let app = koa()

app.use(function * (next) {
  console.log("generate1----start")
  yield next
  console.log("generate1----end")
})

app.use(function * (next) {
  console.log("generate2----start")
  yield next
  console.log("generate2----end")
  this.body = "hello world"
})

app.listen(3000)

用過koa的同學(xué)都知道添加中間件的方式是使用koa實(shí)例的use方法,并傳入一個(gè)generator函數(shù),這個(gè)generator函數(shù)可以接受一個(gè)next(這個(gè)next到底是啥?這里先不闡明,在后面會仔細(xì)說明)。

執(zhí)行use干了嘛

這是koa的構(gòu)造函數(shù),為了沒有其他信息的干擾,我去除了一些暫時(shí)用不到的代碼,這里我們把目光聚焦在middleware這個(gè)數(shù)組即可。

function Application() {
  // xxx
  this.middleware = []; // 這個(gè)數(shù)組就是用來裝一個(gè)個(gè)中間件的
  // xxx
}

接下來我們要看use方法了

同樣去除了一些暫時(shí)不用的代碼,可以看到每次執(zhí)行use方法,就把外面?zhèn)鬟M(jìn)來的generator函數(shù)push到middleware數(shù)組中

app.use = function(fn){
  // xxx
  this.middleware.push(fn);
  // xxx
};

好啦!你已經(jīng)知道koa中是預(yù)先通過use方法,將請求可能會經(jīng)過的中間件裝在了一個(gè)數(shù)組中。

接下來我們要開始本文的重點(diǎn)了,當(dāng)一個(gè)請求到來的時(shí)候,是怎樣經(jīng)過中間件,怎么跑起來的

首先我們只要知道下面這段callback函數(shù)就是請求到來的時(shí)候執(zhí)行的回調(diào)即可(同樣盡量去除了我們不用的代碼)

app.callback = function(){
  // xxx

  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));

  // xxx

  return function(req, res){
    // xxx

    fn.call(ctx).then(function () {
      respond.call(ctx);
    }).catch(ctx.onerror);

    // xxx
  }
};

這段代碼可以分成兩個(gè)部分

請求前的中間件初始化處理部分

請求到來時(shí)的中間件運(yùn)行部分

我們分部分來說一下

var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));

這段代碼對experimental做了下判斷,如果設(shè)置為了true那么koa中將可以支持傳入async函數(shù),否則就執(zhí)行co.wrap(compose(this.middleware))。

只有一行初始化中間件就做完啦?

我知道koa很屌,但也別這么屌好不好,所以說評價(jià)一個(gè)好的程序員不是由代碼量決定的

我們來看下這段代碼到底有什么神奇的地方

compose(this.middleware)

把裝著中間件middleware的數(shù)組作為參數(shù)傳進(jìn)了compose這個(gè)方法,那么compose做了什么事呢?其實(shí)就是把原本毫無關(guān)系的一個(gè)個(gè)中間件給首尾串起來了,于是他們之間就有了千絲萬縷的聯(lián)系。

function compose(middleware){
  return function *(next){
    // 第一次得到next是由于*noop生成的generator對象
    if (!next) next = noop(); 

    var i = middleware.length;
    // 從后往前開始執(zhí)行middleware中的generator函數(shù)
    while (i--) {
      // 把后一個(gè)中間件得到的generator對象傳給前一個(gè)作為第一個(gè)參數(shù)存在
      next = middleware[i].call(this, next);
    }
    
    return yield *next;
  }
}

function *noop(){}

文字解釋一下就是,compose將中間件從最后一個(gè)開始處理,并一直往前直到第一個(gè)中間件。其中非常關(guān)鍵的就是將后一個(gè)中間件得到generator對象作為參數(shù)(這個(gè)參數(shù)就是文章開頭說到的next啦,也就是說next其實(shí)是一個(gè)generator對象)傳給前一個(gè)中間件。當(dāng)然最后一個(gè)中間件的參數(shù)next是一個(gè)空的generator函數(shù)生成的對象。

我們自己來寫一個(gè)簡單的例子說明compose是如何將多個(gè)generator函數(shù)串聯(lián)起來的

function * gen1 (next) {
  yield "gen1"
  yield * next // 開始執(zhí)行下一個(gè)中間件
  yield "gen1-end" // 下一個(gè)中間件執(zhí)行完成再繼續(xù)執(zhí)行g(shù)en1中間件的邏輯
}

function * gen2 (next) {
  yield "gen2"
  yield * next // 開始執(zhí)行下一個(gè)中間件
  yield "gen2-end" // 下一個(gè)中間件執(zhí)行完成再繼續(xù)執(zhí)行g(shù)en2中間件的邏輯
}

function * gen3 (next) {
  yield "gen3"
  yield * next // 開始執(zhí)行下一個(gè)中間件
  yield "gen3-end" // 下一個(gè)中間件執(zhí)行完成再繼續(xù)執(zhí)行g(shù)en3中間件的邏輯
}

function * noop () {}

var middleware = [gen1, gen2, gen3]
var len = middleware.length
var next = noop() // 提供給最后一個(gè)中間件的參數(shù)

while(len--) {
  next = middleware[len].call(null, next)
}

function * letGo (next) {
  yield * next
}

var g = letGo(next)

g.next() // {value: "gen1", done: false}
g.next() // {value: "gen2", done: false}
g.next() // {value: "gen3", done: false}
g.next() // {value: "gen3-end", done: false}
g.next() // {value: "gen2-end", done: false}
g.next() // {value: "gen1-end", done: false}
g.next() // {value: undefined, done: true}

看到了嗎?中間件被串起來之后執(zhí)行的順序是

gen1 -> gen2 -> gen3 -> noop -> gen3 -> gen2 -> gen1

從而首尾相連,進(jìn)而發(fā)生了關(guān)系?。

co.wrap

通過compose處理后返回了一個(gè)generator函數(shù)。

co.wrap(compose(this.middleware))

所有上述代碼可以理解為

co.wrap(function * gen ())

好,我們再看看co.wrap做了什么,慢慢地一步步靠近了哦

co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
}

可以看到co.wrap返回了一個(gè)普通函數(shù)createPromise,這個(gè)函數(shù)就是文章開頭的fn啦。

var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));
中間件開始跑起來啦

前面已經(jīng)說完了,中間件是如何初始化的,即如果由不相干到關(guān)系密切了,接下來開始說請求到來時(shí),初始化好的中間件是怎么跑的。

fn.call(ctx).then(function () {
  respond.call(ctx);
}).catch(ctx.onerror);

這一段便是請求到來手即將要經(jīng)過的中間件執(zhí)行部分,fn執(zhí)行之后返回的是一個(gè)Promise,koa通過注冊成功和失敗的回調(diào)函數(shù)來分別處理請求。

讓我們回到

co.wrap = function (fn) {
  // xxx
  
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
}

createPromise里面的fn就是經(jīng)過compose處理中間件后返回的一個(gè)generator函數(shù),那么執(zhí)行之后拿到的就是一個(gè)generator對象了,并把這個(gè)對象傳經(jīng)經(jīng)典的co里面啦。如果你需要對co的源碼了解歡迎查看昨天寫的走一步再走一步,揭開co的神秘面紗,好了,接下來就是看co里面如何處理這個(gè)被compose處理過的generator對象了

再回顧一下co

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1)

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  return new Promise(function(resolve, reject) {
    if (typeof gen === "function") gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== "function") return resolve(gen);

    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */

    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError("You may only yield a function, promise, generator, array, or object, "
        + "but the following object was passed: "" + String(ret.value) + """));
    }
  });
}


我們直接看一下onFulfilled,這個(gè)時(shí)候第一次進(jìn)co的時(shí)候因?yàn)橐呀?jīng)是generator對象所以會直接執(zhí)行onFulfilled()

function onFulfilled(res) {
  var ret;
  try {
    ret = gen.next(res);
  } catch (e) {
    return reject(e);
  }
  next(ret);
}

gen.next正是用于去執(zhí)行中間件的業(yè)務(wù)邏輯,當(dāng)遇到y(tǒng)ield語句的時(shí)候,將緊隨其后的結(jié)果返回賦值給ret,通常這里的ret,就是我們文中說道的next,也就是當(dāng)前中間件的下一個(gè)中間件。

拿到下一個(gè)中間件后把他交給next去處理

function next(ret) {
  if (ret.done) return resolve(ret.value);
  var value = toPromise.call(ctx, ret.value);
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  return onRejected(new TypeError("You may only yield a function, promise, generator, array, or object, "
    + "but the following object was passed: "" + String(ret.value) + """));
}

當(dāng)中間件執(zhí)行結(jié)束了,就把Promise的狀態(tài)設(shè)置為成功。否則就將ret(也就是下一個(gè)中間件)再用co包一次。主要看toPromise的這幾行代碼即可

function toPromise(obj) {
  // xxx
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // xxx
}

注意噢toPromise這個(gè)時(shí)候的返回值是一個(gè)Promise,這個(gè)非常關(guān)鍵,是下一個(gè)中間件執(zhí)行完成之后回溯到上一個(gè)中間件中斷執(zhí)行處繼續(xù)執(zhí)行的關(guān)鍵

function next(ret) {
  // xxx
  var value = toPromise.call(ctx, ret.value);
  // 即通過前面toPromise返回的Promise實(shí)現(xiàn),當(dāng)后一個(gè)中間件執(zhí)行結(jié)束,回退到上一個(gè)中間件中斷處繼續(xù)執(zhí)行
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 
  // xxx 
}

看到這里,我們可以總結(jié)出,幾乎koa的中間件都會被co給包裝一次,而每一個(gè)中間件又可以通過Promise的then去監(jiān)測其后一個(gè)中間件是否結(jié)束,后一個(gè)中間件結(jié)束后會執(zhí)行前一個(gè)中間件用then監(jiān)聽的操作,這個(gè)操作便是執(zhí)行該中間件yield next后面的那些代碼

打個(gè)比方:

當(dāng)koa中接收到一個(gè)請求的時(shí)候,請求將經(jīng)過兩個(gè)中間件,分別是中間件1中間件2,

中間件1

// 中間件1在yield 中間件2之前的代碼

yield 中間件2

// 中間件2執(zhí)行完成之后繼續(xù)執(zhí)行中間件1的代碼

中間件2

// 中間件2在yield noop中間件之前的代碼

yield noop中間件

// noop中間件執(zhí)行完成之后繼續(xù)執(zhí)行中間件2的代碼

那么處理的過程就是co會立即調(diào)用onFulfilled來執(zhí)行中間件1前半部分代碼,遇到yield 中間件2的時(shí)候得到中間件2generator對象,緊接著,又把這個(gè)對象放到co里面繼續(xù)執(zhí)行一遍,以此類推下去知道最后一個(gè)中間件(我們這里的指的是那個(gè)空的noop中間件)執(zhí)行結(jié)束,繼而馬上調(diào)用promise的resolve方法表示結(jié)束,ok,這個(gè)時(shí)候中間件2監(jiān)聽到noop執(zhí)行結(jié)束了,馬上又去執(zhí)行了onFulfilled來執(zhí)行yield noop中間件后半部分代碼,好啦這個(gè)時(shí)候中間件2也執(zhí)行結(jié)束了,也會馬上調(diào)用promise的resolve方法表示結(jié)束,ok,這個(gè)時(shí)候中間件1監(jiān)聽到中間件2執(zhí)行結(jié)束了,馬上又去執(zhí)行了onFulfilled來執(zhí)行yield 中間件2后半部分代碼,最后中間件全部執(zhí)行完了,就執(zhí)行respond.call(ctx);

啊 啊 啊好繞,不過慢慢看,仔細(xì)想,還是可以想明白的。用代碼表示這個(gè)過程有點(diǎn)類似

new Promise((resolve, reject) => {
  // 我是中間件1
  yield new Promise((resolve, reject) => {
    // 我是中間件2
    yield new Promise((resolve, reject) => {
      // 我是body
    })
    // 我是中間件2
  })
  // 我是中間件1
});

結(jié)尾

羅里吧嗦說了一大堆,也不知道有沒有把執(zhí)行原理說明白。

如果對你理解koa有些許幫助,不介意的話,點(diǎn)擊源碼地址點(diǎn)顆小星星吧

如果對你理解koa有些許幫助,不介意的話,點(diǎn)擊源碼地址點(diǎn)顆小星星吧

如果對你理解koa有些許幫助,不介意的話,點(diǎn)擊源碼地址點(diǎn)顆小星星吧

源碼地址

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/83062.html

相關(guān)文章

  • Koa源碼解析

    摘要:若大于時(shí),將賦予,此時(shí)與相等。通過源碼分析,我們知道了的核心思想建立于中間件機(jī)制,它是一個(gè)設(shè)計(jì)十分簡潔巧妙的框架,擴(kuò)展性極強(qiáng),就是建立于之上的上層框架。 Koa是一款設(shè)計(jì)優(yōu)雅的輕量級Node.js框架,它主要提供了一套巧妙的中間件機(jī)制與簡練的API封裝,因此源碼閱讀起來也十分輕松,不論你從事前端或是后端研發(fā),相信都會有所收獲。 目錄結(jié)構(gòu) 首先將源碼下載到本地,可以看到Koa的源碼只包含...

    CarterLi 評論0 收藏0
  • 前端面試題大集合:來自真實(shí)大廠的532道面試題(只有題,沒有答案)

    答案自己谷歌或百度找。 一、來源背景 面試題是來自微博@牛客網(wǎng)發(fā)布的真實(shí)大廠前端面經(jīng)題目,我一直在收集題目長期一個(gè)一個(gè)的記錄下來的,可能會有重復(fù),但基本前端的面試大綱和需要掌握的知識都在其中了,面試題僅做學(xué)習(xí)參考,學(xué)習(xí)者閱后也要用心鉆研其中的原理,重要知識需要系統(tǒng)學(xué)習(xí)、透徹學(xué)習(xí),形成自己的知識鏈。 二、532道前端真實(shí)大廠面試題 express和koa的對比,兩者中間件的原理,koa捕獲異常多種情...

    Kerr1Gan 評論0 收藏0
  • 前端面試題大集合:來自真實(shí)大廠的532道面試題(只有題,沒有答案)

    答案自己谷歌或百度找。 一、來源背景 面試題是來自微博@??途W(wǎng)發(fā)布的真實(shí)大廠前端面經(jīng)題目,我一直在收集題目長期一個(gè)一個(gè)的記錄下來的,可能會有重復(fù),但基本前端的面試大綱和需要掌握的知識都在其中了,面試題僅做學(xué)習(xí)參考,學(xué)習(xí)者閱后也要用心鉆研其中的原理,重要知識需要系統(tǒng)學(xué)習(xí)、透徹學(xué)習(xí),形成自己的知識鏈。 二、532道前端真實(shí)大廠面試題 express和koa的對比,兩者中間件的原理,koa捕獲異常多種情...

    lushan 評論0 收藏0
  • 前端面試題大集合:來自真實(shí)大廠的532道面試題(只有題,沒有答案)

    答案自己谷歌或百度找。 一、來源背景 面試題是來自微博@??途W(wǎng)發(fā)布的真實(shí)大廠前端面經(jīng)題目,我一直在收集題目長期一個(gè)一個(gè)的記錄下來的,可能會有重復(fù),但基本前端的面試大綱和需要掌握的知識都在其中了,面試題僅做學(xué)習(xí)參考,學(xué)習(xí)者閱后也要用心鉆研其中的原理,重要知識需要系統(tǒng)學(xué)習(xí)、透徹學(xué)習(xí),形成自己的知識鏈。 二、532道前端真實(shí)大廠面試題 express和koa的對比,兩者中間件的原理,koa捕獲異常多種情...

    joyvw 評論0 收藏0

發(fā)表評論

0條評論

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