<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      深入理解 JavaScript 異步系列(4)—— Generator

      第一部分,ES6 中的 Generator

      原文地址 http://www.rzrgm.cn/wangfupeng1988/p/6532713.html 未經作者允許不得轉載~

      在 ES6 出現之前,基本都是各式各樣類似Promise的解決方案來處理異步操作的代碼邏輯,但是 ES6 的Generator卻給異步操作又提供了新的思路,馬上就有人給出了如何用Generator來更加優雅的處理異步操作。

      本節內容概述

      • Generator簡介
      • Generator最終如何處理異步操作
      • 接下來...

      Generator簡介

      先來一段最基礎的Generator代碼

      function* Hello() {
          yield 100
          yield (function () {return 200})()
          return 300
      }
      
      var h = Hello()
      console.log(typeof h)  // object
      
      console.log(h.next())  // { value: 100, done: false }
      console.log(h.next())  // { value: 200, done: false }
      console.log(h.next())  // { value: 300, done: true }
      console.log(h.next())  // { value: undefined, done: true }

      在 nodejs 環境執行這段代碼,打印出來的數據都在代碼注釋中了,也可以自己去試試。將這段代碼簡單分析一下吧

      • 定義Generator時,需要使用function*,其他的和定義函數一樣。內部使用yield,至于yield的用處以后再說
      • 執行var h = Hello()生成一個Generator對象,經驗驗證typeof h發現不是普通的函數
      • 執行Hello()之后,Hello內部的代碼不會立即執行,而是出于一個暫停狀態
      • 執行第一個h.next()時,會激活剛才的暫停狀態,開始執行Hello內部的語句,但是,直到遇到yield語句。一旦遇到yield語句時,它就會將yield后面的表達式執行,并返回執行的結果,然后又立即進入暫停狀態。
      • 因此第一個console.log(h.next())打印出來的是{ value: 100, done: false }value是第一個yield返回的值,done: false表示目前處于暫停狀態,尚未執行結束,還可以再繼續往下執行。
      • 執行第二個h.next()和第一個一樣,不在贅述。此時會執行完第二個yield后面的表達式并返回結果,然后再次進入暫停狀態
      • 執行第三個h.next()時,程序會打破暫停狀態,繼續往下執行,但是遇到的不是yield而是return。這就預示著,即將執行結束了。因此最后返回的是{ value: 300, done: true }done: true表示執行結束,無法再繼續往下執行了。
      • 再去執行第四次h.next()時,就只能得到{ value: undefined, done: true },因為已經結束,沒有返回值了。

      一口氣分析下來,發現并不是那么簡單,雖然這只是一個最最簡單的Generator入門代碼 ———— 可見Generator的學習成本多高 ———— 但是一旦學會,那將受用無窮!別著急,跟著我的節奏慢慢來,一行一行代碼看,你會很快深入了解Genarator

      但是,你要詳細看一下上面的所有步驟,爭取把我寫的每一步都搞明白。如果搞不明白細節,至少要明白以下幾個要點:

      • Generator不是函數,不是函數,不是函數
      • Hello()不會立即出發執行,而是一上來就暫停
      • 每次h.next()都會打破暫停狀態去執行,直到遇到下一個yield或者return
      • 遇到yield時,會執行yeild后面的表達式,并返回執行之后的值,然后再次進入暫停狀態,此時done: false
      • 遇到return時,會返回值,執行結束,即done: true
      • 每次h.next()的返回值永遠都是{value: ... , done: ...}的形式

      Generator最終如何處理異步操作

      上面只是一個最基本最簡單的介紹,但是我們看不到任何與異步操作相關的事情,那我們接下來就先展示一下最終我們將使用Generator如何做異步操作。

      之前講解Promise時候,依次讀取多個文件,我們是這么操作的(看不明白的需要回爐重造哈),主要是使用then做鏈式操作。

      readFilePromise('some1.json').then(data => {
          console.log(data)  // 打印第 1 個文件內容
          return readFilePromise('some2.json')
      }).then(data => {
          console.log(data)  // 打印第 2 個文件內容
          return readFilePromise('some3.json')
      }).then(data => {
          console.log(data)  // 打印第 3 個文件內容
          return readFilePromise('some4.json')
      }).then(data=> {
          console.log(data)  // 打印第 4 個文件內容
      })

      而如果學會Generator那么讀取多個文件就是如下這樣寫。先不要管如何實現的,光看一看代碼,你就能比較出哪個更加簡潔、更加易讀、更加所謂的優雅!

      co(function* () {
          const r1 = yield readFilePromise('some1.json')
          console.log(r1)  // 打印第 1 個文件內容
          const r2 = yield readFilePromise('some2.json')
          console.log(r2)  // 打印第 2 個文件內容
          const r3 = yield readFilePromise('some3.json')
          console.log(r3)  // 打印第 3 個文件內容
          const r4 = yield readFilePromise('some4.json')
          console.log(r4)  // 打印第 4 個文件內容
      })

      不過,要學到這一步,還需要很長的路要走。不過不要驚慌,也不要請如來佛祖,跟著我的節奏來,認真看,一天包教包會是沒問題的!

      接下來...

      接下來我們不會立刻講解如何使用Generator做異步操作,而是看一看Generator是一個什么東西!說來話長,這要從 ES6 的另一個概念Iterator說起。

      第二部分,Iterator 遍歷器

      ES6 中引入了很多此前沒有但是卻非常重要的概念,Iterator就是其中一個。Iterator對象是一個指針對象,實現類似于單項鏈表的數據結構,通過next()將指針指向下一個節點 ———— 這里也就是先簡單做一個概念性的介紹,后面將通過實例為大家演示。

      本節演示的代碼可參考這里

      本節內容概述

      • 簡介Symbol數據類型
      • 具有[Symbol.iterator]屬性的數據類型
      • 生成Iterator對象
      • Generator返回的也是Iterator對象
      • 接下來...

      簡介Symbol數據類型

      Symbol是一個特殊的數據類型,和number string等并列,詳細的教程可參考阮一峰老師 ES6 入門的 Symbol 篇。先看兩句程序

      console.log(Array.prototype.slice)  // [Function: slice]
      console.log(Array.prototype[Symbol.iterator])  // [Function: values]

      數組的slice屬性大家都比較熟悉了,就是一個函數,可以通過Array.prototype.slice得到。這里的slice是一個字符串,但是我們獲取Array.prototype[Symbol.iterator]可以得到一個函數,只不過這里的[Symbol.iterator]Symbol數據類型,不是字符串。但是沒關系,Symbol數據類型也可以作為對象屬性的key。如下:

      var obj = {}
      obj.a = 100
      obj[Symbol.iterator] = 200
      console.log(obj)  // {a: 100, Symbol(Symbol.iterator): 200}

      在此小節中,你只需要知道[Symbol.iterator]是一個特殊的數據類型Symbol類型,但是也可以像number string類型一樣,作為對象的屬性key來使用

      原生具有[Symbol.iterator]屬性的數據類型

      在 ES6 中,原生具有[Symbol.iterator]屬性數據類型有:數組、某些類似數組的對象(如argumentsNodeList)、SetMap。其中,SetMap也是 ES6 中新增的數據類型。

      // 數組
      console.log([1, 2, 3][Symbol.iterator])  // function values() { [native code] }
      // 某些類似數組的對象,NoeList
      console.log(document.getElementsByTagName('div')[Symbol.iterator])  // function values() { [native code] }

      原生具有[Symbol.iterator]屬性數據類型有一個特點,就是可以使用for...of來取值,例如

      var item
      for (item of [100, 200, 300]) {
          console.log(item)
      }
      // 打印出:100 200 300 
      // 注意,這里每次獲取的 item 是數組的 value,而不是 index ,這一點和 傳統 for 循環以及 for...in 完全不一樣

      而具有[Symbol.iterator]屬性的對象,都可以一鍵生成一個Iterator對象。如何生成以及生成之后什么樣子,還有生成之后的作用,下文分解。

      不要著急,也不要跳過本文的任何步驟,一步一步跟著我的節奏來看。

      生成Iterator對象

      定義一個數組,然后生成數組的Iterator對象

      const arr = [100, 200, 300]
      const iterator = arr[Symbol.iterator]()  // 通過執行 [Symbol.iterator] 的屬性值(函數)來返回一個 iterator 對象

      好,現在生成了iterator,那么該如何使用它呢 ———— 有兩種方式:nextfor...of

      先說第一種,next

      console.log(iterator.next())  // { value: 100, done: false }
      console.log(iterator.next())  // { value: 200, done: false }
      console.log(iterator.next())  // { value: 300, done: false }
      console.log(iterator.next())  // { value: undefined, done: true }

      看到這里,再結合上一節內容,是不是似曾相識的感覺?(額,沒有的話,那你就回去重新看上一節的內容吧) iterator對象可以通過next()方法逐步獲取每個元素的值,以{ value: ..., done: ... }形式返回,value就是值,done表示是否到已經獲取完成。

      再說第二種,for...of

      let i
      for (i of iterator) {
          console.log(i)
      }
      // 打印:100 200 300 

      上面使用for...of遍歷iterator對象,可以直接將其值獲取出來。這里的“值”就對應著上面next()返回的結果的value屬性

      Generator返回的也是Iterator對象

      看到這里,你大體也應該明白了,上一節演示的Generator,就是生成一個Iterator對象。因此才會有next(),也可以通過for...of來遍歷。拿出上一節的例子再做一次演示:

      function* Hello() {
          yield 100
          yield (function () {return 200})()
          return 300 
      }
      const h = Hello()
      console.log(h[Symbol.iterator])  // [Function: [Symbol.iterator]]

      執行const h = Hello()得到的就是一個iterator對象,因為h[Symbol.iterator]是有值的。既然是iterator對象,那么就可以使用next()for...of進行操作

      console.log(h.next())  // { value: 100, done: false }
      console.log(h.next())  // { value: 200, done: false }
      console.log(h.next())  // { value: 300, done: false }
      console.log(h.next())  // { value: undefined, done: true }
      
      let i
      for (i of h) {
          console.log(i)
      }

      接下來...

      這一節我們花費很大力氣,從Iterator又回歸到了Generator,目的就是為了看看Generator到底是一個什么東西。了解其本質,才能更好的使用它,否則總有一種抓瞎的感覺。

      接下來我們就Generator具體有哪些使用場景。

       

      第三部分,Generator 的具體應用

      前面用兩節的內容介紹了Generator可以讓執行處于暫停狀態,并且知道了Generator返回的是一個Iterator對象,這一節就詳細介紹一下Generator的一些基本用法。

      本節演示的代碼可參考這里

      本節內容概述

      • nextyield參數傳遞
      • for...of的應用示例
      • yield*語句
      • Generator中的this
      • 接下來...

      nextyield參數傳遞

      我們之前已經知道,yield具有返回數據的功能,如下代碼。yield后面的數據被返回,存放到返回結果中的value屬性中。這算是一個方向的參數傳遞。

      function* G() {
          yield 100
      }
      const g = G()
      console.log( g.next() ) // {value: 100, done: false}

      還有另外一個方向的參數傳遞,就是nextyield傳遞,如下代碼。

      function* G() {
          const a = yield 100
          console.log('a', a)  // a aaa
          const b = yield 200
          console.log('b', b)  // b bbb
          const c = yield 300
          console.log('c', c)  // c ccc
      }
      const g = G()
      g.next()    // value: 100, done: false
      g.next('aaa') // value: 200, done: false
      g.next('bbb') // value: 300, done: false
      g.next('ccc') // value: undefined, done: true

      捋一捋上面代碼的執行過程:

      • 執行第一個g.next()時,為傳遞任何參數,返回的{value: 100, done: false},這個應該沒有疑問
      • 執行第二個g.next('aaa')時,傳遞的參數是'aaa',這個'aaa'就會被賦值到G內部的a標量中,然后執行console.log('a', a)打印出來,最后返回{value: 200, done: false}
      • 執行第三個、第四個時,道理都是完全一樣的,大家自己捋一捋。

      有一個要點需要注意,就g.next('aaa')是將'aaa'傳遞給上一個已經執行完了的yield語句前面的變量,而不是即將執行的yield前面的變量。這句話要能看明白,看不明白就說明剛才的代碼你還沒看懂,繼續看。

      for...of的應用示例

      針對for...ofIterator對象的操作之前已經介紹過了,不過這里用一個非常好的例子來展示一下。用簡單幾行代碼實現斐波那契數列。通過之前學過的Generator知識,應該不能解讀這份代碼。

      function* fibonacci() {
          let [prev, curr] = [0, 1]
          for (;;) {
              [prev, curr] = [curr, prev + curr]
              // 將中間值通過 yield 返回,并且保留函數執行的狀態,因此可以非常簡單的實現 fibonacci
              yield curr
          }
      }
      for (let n of fibonacci()) {
          if (n > 1000) {
              break
          }
          console.log(n)
      }

      yield*語句

      如果有兩個Generator,想要在第一個中包含第二個,如下需求:

      function* G1() {
          yield 'a'
          yield 'b'
      }
      function* G2() {
          yield 'x'
          yield 'y'
      }

      針對以上兩個Generator,我的需求是:一次輸出a x y b,該如何做?有同學看到這里想起了剛剛學到的for..of可以實現————不錯,確實可以實現(大家也可以想想到底該如何實現)

      但是,這要演示一個更加簡潔的方式yield*表達式

      function* G1() {
          yield 'a'
          yield* G2()  // 使用 yield* 執行 G2()
          yield 'b'
      }
      function* G2() {
          yield 'x'
          yield 'y'
      }
      for (let item of G1()) {
          console.log(item)
      }

      之前學過的yield后面會接一個普通的 JS 對象,而yield*后面會接一個Generator,而且會把它其中的yield按照規則來一步一步執行。如果有多個Generator串聯使用的話(例如Koa源碼中),用yield*來操作非常方便。

      Generator中的this

      對于以下這種寫法,大家可能會和構造函數創建對象的寫法產生混淆,這里一定要注意 —— Generator 不是函數,更不是構造函數

      function* G() {}
      const g = G()

      而以下這種寫法,更加不會成功。只有構造函數才會這么用,構造函數返回的是this,而Generator返回的是一個Iterator對象。完全是兩碼事,千萬不要搞混了。

      function* G() {
          this.a = 10
      }
      const g = G()
      console.log(g.a) // 報錯

      接下來...

      本節基本介紹了Generator的最常見的用法,但是還是沒有和咱們的最終目的————異步操作————沾上關系,而且現在看來有點八竿子打不著的關系。但是話說回來,這幾節內容,你也學到了不少知識啊。

      別急哈,即便是下一節,它們還不會有聯系,再下一節就真相大白了。下一節我們又給出一個新概念————Thunk函數

       

      第四部分,Thunk 函數

      要想讓Generator和異步操作產生聯系,就必須過thunk函數這一關。這一關過了之后,立即就可以著手異步操作的事情,因此大家再堅持堅持。至于thunk函數是什么,下文會詳細演示。

      本節演示的代碼可參考這里

      本節內容概述

      • 一個普通的異步函數
      • 封裝成一個thunk函數
      • thunk函數的特點
      • 使用thunkify
      • 接下來...

      一個普通的異步函數

      就用 nodejs 中讀取文件的函數為例,通常都這么寫

      fs.readFile('data1.json', 'utf-8', (err, data) => {
          // 獲取文件內容
      })

      其實這個寫法就是將三個參數都傳遞給fs.readFile這個方法,其中最后一個參數是一個callback函數。這種函數叫做 多參數函數,我們接下來做一個改造

      封裝成一個thunk函數

      改造的代碼如下所示。不過是不是感覺越改造越復雜了?不過請相信:你看到的復雜僅僅是表面的,這一點東西變的復雜,是為了讓以后更加復雜的東西變得簡單。對于個體而言,隨性比較簡單,遵守規則比較復雜;但是對于整體(包含很多個體)而言,大家都隨性就不好控制了,而大家都遵守規則就很容易管理 ———— 就是這個道理!

      const thunk = function (fileName, codeType) {
          // 返回一個只接受 callback 參數的函數
          return function (callback) {
              fs.readFile(fileName, codeType, callback)
          }
      }
      const readFileThunk = thunk('data1.json', 'utf-8')
      readFileThunk((err, data) => {
          // 獲取文件內容
      })

      先自己看一看以上代碼,應該是能看懂的,但是你可能就是看懂了卻不知道這么做的意義在哪里。意義先不管,先把它看懂,意義下一節就會看到。

      • 執行const readFileThunk = thunk('data1.json', 'utf-8')返回的其實是一個函數
      • readFileThunk這個函數,只接受一個參數,而且這個參數是一個callback函數

      thunk函數的特點

      就上上面的代碼,我們經過對傳統的異步操作函數進行封裝,得到一個只有一個參數的函數,而且這個參數是一個callback函數,那這就是一個thunk函數。就像上面代碼中readFileThunk一樣。

      使用thunkify

      上面代碼的封裝,是我們手動來做的,但是沒遇到一個情況就需要手動做嗎?在這個開源的時代當讓不會這樣,直接使用第三方的thunkify就好了。

      首先要安裝npm i thunkify --save,然后在代碼的最上方引用const thunkify = require('thunkify')。最后,上面我們手動寫的代碼,完全可以簡化成這幾行,非常簡單!

      const thunk = thunkify(fs.readFile)
      const readFileThunk = thunk('data1.json', 'utf-8')
      readFileThunk((err, data) => {
          // 獲取文件內容
      })

      接下來...

      了解了thunk函數,我們立刻就將Generator和異步操作進行結合

       

      第五部分,Generator 與異步操作

      這一節正式開始講解Generator如何進行異步操作,以前我們花了好幾節的時間各種打基礎,現在估計大家也都等急了,好戲馬上開始!

      本節演示的代碼可參考這里

      本節內容概述

      • Genertor中使用thunk函數
      • 挨個讀取兩個文件的內容
      • 自驅動流程
      • 使用co
      • co庫和Promise
      • 接下來...

      Genertor中使用thunk函數

      這個比較簡單了,之前都講過的,直接看代碼即可。代碼中表達的意思,是要依次讀取兩個文件的內容

      const readFileThunk = thunkify(fs.readFile)
      const gen = function* () {
          const r1 = yield readFileThunk('data1.json')
          console.log(r1)
          const r2 = yield readFileThunk('data2.json')
          console.log(r2)
      }

      挨個讀取兩個文件的內容

      接著以上的代碼繼續寫,注釋寫的非常詳細,大家自己去看,看完自己寫代碼親身體驗。

      const g = gen()
      
      // 試著打印 g.next() 這里一定要明白 value 是一個 thunk函數 ,否則下面的代碼你都看不懂
      // console.log( g.next() )  // g.next() 返回 {{ value: thunk函數, done: false }} 
      
      // 下一行中,g.next().value 是一個 thunk 函數,它需要一個 callback 函數作為參數傳遞進去
      g.next().value((err, data1) => {
          // 這里的 data1 獲取的就是第一個文件的內容。下一行中,g.next(data1) 可以將數據傳遞給上面的 r1 變量,此前已經講過這種參數傳遞的形式
          // 下一行中,g.next(data1).value 又是一個 thunk 函數,它又需要一個 callback 函數作為參數傳遞進去
          g.next(data1).value((err, data2) => {
              // 這里的 data2 獲取的是第二個文件的內容,通過 g.next(data2) 將數據傳遞個上面的 r2 變量
              g.next(data2)
          })
      })

      上面 6 行左右的代碼,卻用了 6 行左右的注釋來解釋,可見代碼的邏輯并不簡單,不過你還是要去盡力理解,否則接下來的內容無法繼續。再說,我已經寫的那么詳細了,你只要照著仔細看肯定能看明白的。

      也許上面的代碼給你帶來的感覺并不好,第一它邏輯復雜,第二它也不是那么易讀、簡潔呀,用Generator實現異步操作就是這個樣子的?———— 當然不是,繼續往下看。

      自驅動流程

      以上代碼中,讀取兩個文件的內容都是手動一行一行寫的,而我們接下來要做一個自驅動的流程,定義好Generator的代碼之后,就讓它自動執行。完整的代碼如下所示:

      // 自動流程管理的函數
      function run(generator) {
          const g = generator()
          function next(err, data) {
              const result = g.next(data)  // 返回 { value: thunk函數, done: ... }
              if (result.done) {
                  // result.done 表示是否結束,如果結束了那就 return 作罷
                  return
              }
              result.value(next)  // result.value 是一個 thunk 函數,需要一個 callback 函數作為參數,而 next 就是一個 callback 形式的函數
          }
          next() // 手動執行以啟動第一次 next
      }
      
      // 定義 Generator
      const readFileThunk = thunkify(fs.readFile)
      const gen = function* () {
          const r1 = yield readFileThunk('data1.json')
          console.log(r1.toString())
          const r2 = yield readFileThunk('data2.json')
          console.log(r2.toString())
      }
      
      // 啟動執行
      run(gen)

      其實這段代碼和上面的手動編寫讀取兩個文件內容的代碼,原理上是一模一樣的,只不過這里把流程驅動給封裝起來了。我們簡單分析一下這段代碼

      • 最后一行run(gen)之后,進入run函數內部執行
      • const g = generator()創建Generator實例,然后定義一個next方法,并且立即執行next()
      • 注意這個next函數的參數是err, data兩個,和我們fs.readFile用到的callback函數形式完全一樣
      • 第一次執行next時,會執行const result = g.next(data),而g.next(data)返回的是{ value: thunk函數, done: ... }value是一個thunk函數,done表示是否結束
      • 如果done: true,那就直接return了,否則繼續進行
      • result.value是一個thunk函數,需要接受一個callback函數作為參數傳遞進去,因此正好把next給傳遞進去,讓next一直被執行下去

      大家照著這個過程來捋一捋,不是特別麻煩,然后自己試著寫完運行一下,基本就能了解了。

      使用co

      剛才我們定義了一個run還是來做自助流程管理,是不是每次使用都得寫一遍run函數呢?———— 肯定不是的,直接用大名鼎鼎的co就好了。用Generator的工程師,肯定需要用到co,兩者天生一對,難舍難分。

      使用之前請安裝npm i co --save,然后在文件開頭引用const co = require('co')co到底有多好用,我們將剛才的代碼用co重寫,就變成了如下代碼。非常簡潔

      // 定義 Generator
      const readFileThunk = thunkify(fs.readFile)
      const gen = function* () {
          const r1 = yield readFileThunk('data1.json')
          console.log(r1.toString())
          const r2 = yield readFileThunk('data2.json')
          console.log(r2.toString())
      }
      const c = co(gen)

      而且const c = co(gen)返回的是一個Promise對象,可以接著這么寫

      c.then(data => {
          console.log('結束')
      })

      co庫和Promise

      剛才提到co()最終返回的是Promise對象,后知后覺,我們已經忘記Promise好久了,現在要重新把它拾起來。如果使用co來處理Generator的話,其實yield后面可以跟thunk函數,也可以跟Promise對象。

      thunk函數上文一直在演示,下面演示一下Promise對象的,也權當再回顧一下久別的Promise。其實從形式上和結果上,都跟thunk函數一樣。

      const readFilePromise = Q.denodeify(fs.readFile)
      
      const gen = function* () {
          const r1 = yield readFilePromise('data1.json')
          console.log(r1.toString())
          const r2 = yield readFilePromise('data2.json')
          console.log(r2.toString())
      }
      
      co(gen)

      接下來...

      經過了前幾節的技術積累,我們用一節的時間就講述了Generator如何進行異步操作。接下來要介紹一個開源社區中比較典型的使用Generator的框架 ———— Koa

       

      第六部分,koa 中使用 Generator

      koa 是一個 nodejs 開發的 web 框架,所謂 web 框架就是處理 http 請求的。開源的 nodejs 開發的 web 框架最初是 express

      我們此前說過,既然是處理 http 請求,是一種網絡操作,肯定就會用到異步操作。express 使用的異步操作是傳統的callbck,而 koa 用的是我們剛剛講的Generator(koa v1.x用的是Generator,已經被廣泛使用,而 koa v2.x用到了 ES7 中的async-await,不過因為 ES7 沒有正式發布,所以 koa v2.x也沒有正式發布,不過可以試用)

      koa 是由 express 的原班開發人員開發的,比 express 更加簡潔易用,因此 koa 是目前最為推薦的 nodejs web 框架。阿里前不久就依賴于 koa 開發了自己的 nodejs web 框架 egg

      國內可以通過koa.bootcss.com查閱文檔,不過這網站依賴了 Google 的服務,因此如果不科學上網,估計會訪問會很慢

      提醒:如果你是初學Generator而且從來沒有用過 koa ,那么這一節你如果看不懂,沒有問題。看不懂就不要強求,可以忽略,繼續往下看!

      本節演示的代碼可參考這里

      本節內容概述

      • koa 中如何應用Generator
      • koa 的這種應用機制是如何實現的
      • 接下來...

      koa 中如何應用Generator

      koa 是一個 web 框架,處理 http 請求,但是這里我們不去管它如何處理 http 請求,而是直接關注它使用Genertor的部分————中間件。

      例如,我們現在要用 3 個Generator輸出12345,我們如下代碼這么寫。應該能看明白吧?看不明白回爐重造!

      let info = ''
      function* g1() {
          info += '1'  // 拼接 1
          yield* g2()  // 拼接 234
          info += '5'  // 拼接 5
      }
      function* g2() {
          info += '2'  // 拼接 2
          yield* g3()  // 拼接 3
          info += '4'  // 拼接 4
      }
      function* g3() {
          info += '3'  // 拼接 3
      }
      
      var g = g1()
      g.next()
      console.log(info)  // 12345

      但是如果用 koa 的 中間件 的思路來做,就需要如下這么寫。

      app.use(function *(next){
          this.body = '1';
          yield next;
          this.body += '5';
          console.log(this.body);
      });
      app.use(function *(next){
          this.body += '2';
          yield next;
          this.body += '4';
      });
      app.use(function *(next){
          this.body += '3';
      });

      解釋幾個關鍵點

      • app.use()中傳入的每一個Generator就是一個 中間件,中間件按照傳入的順序排列,順序不能亂
      • 每個中間件內部,next表示下一個中間件。yield next就是先將程序暫停,先去執行下一個中間件,等next被執行完之后,再回過頭來執行當前代碼的下一行。因此,koa 的中間件執行順序是一種洋蔥圈模型,不過這里看不懂也沒問題。
      • 每個中間件內部,this可以共享變量。即第一個中間件改變了this的屬性,在第二個中間件中可以看到效果。

      koa 的這種應用機制是如何實現的

      前方高能————上面介紹 koa 的中間價估計有些新人就開始蒙圈了,不過接下來還有更加有挑戰難度的,就是以上這種方式是如何實現的。你就盡量去看,看懂了更好,看不懂也沒關系————當然,你完全可以選擇跳過本教程直接去看下一篇,這都 OK

      加入我們自己實現一個簡單的 koa ———— MyKoa ,那么僅需要幾十行代碼就可以搞定上面的問題。直接寫代碼,注意看重點部分的注釋

      class MyKoa extends Object {
          constructor(props) {
              super(props);
      
              // 存儲所有的中間件
              this.middlewares = []
          }
      
          // 注入中間件
          use (generator) {
              this.middlewares.push(generator)
          }
      
          // 執行中間件
          listen () {
              this._run()
          }
      
          _run () {
              const ctx = this
              const middlewares = ctx.middlewares
              co(function* () {
                  let prev = null
                  let i = middlewares.length
                  //從最后一個中間件到第一個中間件的順序開始遍歷
                  while (i--) {
                      // ctx 作為函數執行時的 this 才能保證多個中間件中數據的共享
                      //prev 將前面一個中間件傳遞給當前中間件,才使得中間件里面的 next 指向下一個中間件
                      prev = middlewares[i].call(ctx, prev);
                  }
                  //執行第一個中間件
                  yield prev;
              })
          }
      }

      最后我們執行代碼實驗一下效果

      var app = new MyKoa();
      app.use(function *(next){
          this.body = '1';
          yield next;
          this.body += '5';
          console.log(this.body);  // 12345
      });
      app.use(function *(next){
          this.body += '2';
          yield next;
          this.body += '4';
      });
      app.use(function *(next){
          this.body += '3';
      });
      app.listen();

      接下來...

      Generator的應用基本講完,從一開始的基礎到后面應用到異步操作,再到本節的高級應用 koa ,算是比較全面了。接下來,我們要再回到最初的起點,探討Generator的本質,以及它和callback的關系。

      還是那句話,搞明白原理,才能用的更加出色!

       

      第七部分,Generator 的本質是什么?是否取代了 callback

      其實標題中的問題,是一個偽命題,因為Generatorcallback根本沒有任何關系,只是我們通過一些方式(而且是很復雜的方式)強行將他倆產生了關系,才會有現在的Generator處理異步。

      本節內容概述

      • Generator的本質
      • callback的結合

      Generator的本質

      介紹Generator第一節中,多次提到 暫停 這個詞 ———— “暫停”才是Generator的本質 ———— 只有Generator能讓一段程序執行到指定的位置先暫停,然后再啟動,再暫停,再啟動。

      而這個 暫停 就很容易讓它和異步操作產生聯系,因為我們在處理異步操作時,即需要一種“開始讀取文件,然后暫停一下,等著文件讀取完了,再干嘛干嘛...”這樣的需求。因此將Generator和異步操作聯系在一起,并且產生一些比較簡明的解決方案,這是順其自然的事兒,大家要想明白這個道理。

      不過,JS 還是 JS,單線程還是單線程,異步還是異步,callback還是callback。這一切都不會因為有一個Generator而有任何變化。

      callback的結合

      之前在介紹Promise的最后,拿Promisecallback做過一些比較,最后發現Promise其實是利用了callback才能實現的。而這里,Generator也必須利用callback才能實現。

      拿介紹co時的代碼舉例(代碼如下),如果yield后面用的是thunk函數,那么thunk函數需要的就是一個callback參數。如果yield后面用的是Promise對象,Promisecallback的聯系之前已經介紹過了。

      co(function* () {
          const r1 = yield readFilePromise('some1.json')
          console.log(r1)  // 打印第 1 個文件內容
          const r2 = yield readFileThunk('some2.json')
          console.log(r2)  // 打印第 2 個文件內容
      })

      因此,Generator離不開callbackPromise離不開callback,異步也離不開callback

       

      求打賞

      如果你看完了,感覺還不錯,歡迎給我打賞 ———— 以激勵我更多輸出優質內容

      最后,github地址是 https://github.com/wangfupeng1988/js-async-tutorial 歡迎 star 和 pr

      -------------

      學習作者教程:《前端JS高級面試》《前端JS基礎面試題》《React.js模擬大眾點評webapp》《zepto設計與源碼分析》《json2.js源碼解讀

      posted @ 2017-03-13 09:08  王福朋  閱讀(16195)  評論(12)    收藏  舉報
      主站蜘蛛池模板: 99久久99久久久精品久久| 欧美一区内射最近更新| 亚洲av成人区国产精品| 好吊妞无缓冲视频观看| 在线国产极品尤物你懂的| 日韩少妇人妻vs中文字幕| 丁香五月婷激情综合第九色| 欧洲人与动牲交α欧美精品| 啦啦啦高清在线观看视频www| 精品人妻中文无码av在线| 韩国无码av片在线观看| 国产黄色三级三级看三级| 最新中文字幕国产精品| 日本福利一区二区精品| 亚洲国产美女精品久久久| 亚洲欧美卡通另类丝袜美腿| 黑人好猛厉害爽受不了好大撑| 狠狠色狠狠综合久久| 国产绿帽在线视频看| 亚洲色成人网站www永久男男| 亚洲中文字幕久久精品码| 亚洲人成小说网站色在线 | 国产一区二区高清不卡| 国产视频最新| 色视频在线观看免费视频 | 在线观看免费人成视频色| 阳朔县| 国色天香成人一区二区| 精品人妻一区二区三区蜜臀| 色综合 图片区 小说区| 国产寡妇偷人在线观看| 无码午夜福利片| 日本韩国日韩少妇熟女少妇| 亚洲精品日韩在线丰满| 99视频30精品视频在线观看| 国产午夜福利av在线麻豆| 久久综合激情网| 日韩不卡二区三区三区四区| 少妇熟女久久综合网色欲| 秋霞鲁丝片成人无码| 国产午夜精品亚洲精品国产|