ES6中的迭代器、Generator函式以及Generator函式的非同步操作

青玉伏案發表於2019-05-12

最近在寫RN相關的東西,其中涉及到了redux-saga ,saga的實現原理就是ES6中的Generator函式,而Generator函式又和迭代器有著密不可分的關係。所以本篇部落格先學習總結了iterator相關的東西,然後又介紹了Generator相關的內容,最後介紹了使用Generator進行非同步程式設計。本篇部落格所涉及的示例使用TypeScript語言編寫,當然所涉及的特性是基於ES6規範的,使用TS語言不影響來闡述和總結ES6的相關特性。下篇部落格準備系統梳理一下saga相關的內容。

 

一、迭代器

之前再聊迭代器模式時,使用Swift語言自定義過迭代器,在TS中也有迭代器。此處的迭代器與之前所介紹的迭代器是大同小異的。首先我們先來自定義一個迭代器,然後再看一下ES6中的迭代器的使用方式。

1、自定義迭代器

下方定義了一個迭代器函式,函式說明如下:

  • 該函式接收一個陣列型別的引數,我們可以將需要建立迭代器的陣列作為引數傳進來。
  • 函式內部定義了一個 nextIndex 引數用來記錄迭代器的位置。
  • 該函式返回一個迭代器物件,該迭代器物件包含一個key為 next , value為匿名函式的屬性。
  • 這個key為 next 的匿名方法的返回值為每次迭代器的返回結果物件,這個結果物件由 兩個屬性組成,value表示本次迭代器的值,done表示迭代器遍歷是否結束。
  • 遍歷到最後,最終返回的值為 { value: undefine, done: true }, 也就意味著迭代器遍歷結束,value是undefined, done為true。  

 

自定義完迭代器後,我們就可以對上述程式碼進行測試了。

  • 首先建立了一個陣列,然後將陣列傳給 makeIterator 函式。而 makeIterator 函式會返回一個含有next方法的迭代器物件。
  • 我們將這個迭代器物件命名為 iterator,我們就可以通過 iterator的next方法來依次獲取陣列中的值了。
  • 我們通過 while 迴圈來不斷的呼叫 iterator中的next方法,直到next方法返回的物件中的done值為true時,表示遍歷結束。
  • 遍歷結束後,我們再次呼叫 next() 方法,得到的是{ value: undefind, done: true } 的物件,表示遍歷結束,獲取的value值為 undefined。

 

2、ES6中的迭代器

類似於Swift語言的特性,ES6規範中我們可以直接通過一些物件獲取該物件所對應的迭代器,如下所示:

  • 下方示例中使用的陣列和上面使用的list是一個,首先我們通過 list[Symbol.iterator]() 的方式獲取了 list對應的迭代器。(Symbol也是一種資料型別, 該資料型別用來表示獨一無二的物件)
  • 該迭代器的使用方式和輸出結果與上述我們自定義的迭代器的使用方式完全一致, 輸出結果與之前的結果也是一致的。

 

3、使用 for - of 遍歷迭代器

上述方式建立的迭代器我們是使用的while迴圈來進行遍歷的,除了while迴圈,我們還可以通過for-of 進行遍歷。此處的 for - of遍歷方式類似於Swift語言中的 for - in迴圈,可以依次的自動去除迭代器中的值。下方就是使用for - of 來迴圈遍歷建立的迭代器。

從下方示例中我們不難看出直接輸出的是迭代器返回物件的value值。

 

4、在類中新增迭代器

我們可以在自己的類中新增相關方法,使我們自己的類支援迭代器。下方就建立了一個 RangeIterator 類,該類的作用是可以定義一個範圍,構造器可以接受兩個值,一個是範圍的起始位置另一個是範圍的結束點。下方我們為該範圍類新增了自定義迭代器,具體說明如下:

  • 在該類中新增了一個名為 next 的箭頭函式,在該函式中做的事情與之前我們自定義的next方法差不多,主要是用來獲取下一個值然後返回。
  • 然後又實現一個[Symbole.iterator]函式,用來獲取迭代器物件。
  • 最後我們可看到定義的範圍物件可以向迭代器那樣使用for-of進行遍歷。

 

 

5、呼叫迭代器的場景

迭代器的使用場景還是蠻多的,解構賦值、擴充套件運算子、Generator函式、yield*, 下方會簡單的列舉出來。

(1)、對陣列或者集合的解構賦值

在下方程式碼片段中首先建立了一個名為 mySet 的集合物件。然後通過迴圈給集合中新增了一些值。然後通過 解構賦值 的形式,取出了 mySet 中的第一個值和第二個值。此刻的結構賦值會呼叫集合的迭代器介面,取出第一個值和第二個值,分別賦值給 first 和 second。

第二個紅框中在結構賦值是使用了擴充套件運算子,該操作符會使 others 接收 firstItem 剩下的值。

 

(2)、擴充套件運算子 ...  

接下來來看另一個擴充套件運算子的例子。

  • 首先定義了一個字串,然後通過擴充套件運算子將該字串的每個字元拆分到一個陣列中,輸出結果如下所示。
  • 擴充套件運算子還可以使用到物件上,如第二個示例所示。

 

(3)、在Generator函式的 yield * 中使用

稍後會詳細的介紹 Generator 函式,一個Generator 函式返回的是一個迭代器,我們可以呼叫該迭代器的 next 方法來執行每一個 yield。在 Generator 函式中,可以使用 yield * 後邊跟一個可便遍歷的結構,這樣我們就可以在外部統一使用 next 來訪問這個可遍歷的結構的每一個值,如下所示:

 

 

二、Generator函式及非同步程式設計

理解完迭代器,接下來來看一下Generator函式。如果做過RN開發的話,如果使用過 redux - saga的話,應該對Generator函式不陌生。Generator函式是ES6提供的非同步程式設計的解決方案,解析了我們先看一下Generator函式基本使用方式,再看一下如何使用Generator函式進行非同步程式設計。

1、Generator函式的定義和使用

下方定義了一個 Generator函式,Generator函式的定義與普通函式的定義差不多,只不過是function關鍵字後邊跟了一個*號。然後函式體內部使用了一個個 yield語句來表明每一步的操作。定義完Generator函式後,下方緊接著的是使用,首先呼叫該Generator函式獲取了一個迭代器,每次執行這個迭代器的next方法都會一次的執行一個yield語句。輸出結果和上面的迭代器沒啥區別。

 

2、next的引數

在呼叫Generator函式返回的迭代器時,是可以往next方法中傳入引數的。next 方法可以帶一個引數,該引數被當做上一個 yield 語句的返回值。下方就是給 next 傳參的一個示例:

  • 下方定義了一個Generator函式,用來輸出自增的值,每次呼叫next都會獲取一個自增的值。
  • 當呼叫 rg.next(true) 時,這個true就會被賦值給 reset, 因為這個reset被視為上個yield的返回值,上一個yield執行後,會將index設定為 -1
  • 那麼rg.next(true)對應的 yield執行是,index是從 -1 開始自增的,自增後為0,所以 rg.next(true) 對應的 yield 的值為0。

 

下方是另一個示例:

  • 下方定義了一個名為testNextValue的Generator函式,該函式本身接收了一個引數。
  • 在呼叫該Generator函式時,傳入了一個引數,這個引數不是Next的引數,是Generator函式本身的引數。Generator函式在呼叫時,函式體並不會馬上執行,在呼叫next函式時才會執行函式中yield語句體。
  • 第一次呼叫Next,給Next傳入了一個值 5,也就是說明 x = 5。第一次執行next會呼叫第一個 yield 語句體,test1.next(2) = x + 1 = 5 + 1 = 6, 所以第一次呼叫next的結果值為 6
  • 第二次呼叫 Next,傳入的Next引數為3。這個3 被作為上一個 yield 語句體的返回值,yield(x + 1) 的返回值為 3。那麼 y 的值就為 2 * 3 = 6。yield中的值為 y / 3 = 2所以第二次執行next獲取的值為 2
  • 第三次呼叫Next傳入的引數為 4,這個 4 被作為上個yield語句體返回的引數,所以z = 4, 上分析過了 x = 5, y = 6, 所以 x + y + z = 15, 第三次執行next為 15
  • 再次呼叫Next,因為語句體執行完了,所以獲取到的是undefined。

 

 

三、使用Generator函式進行非同步程式設計

接下來實現一個簡單的示例,使用Generator函式結合Promise回撥模擬一下非同步程式設計。

首先定義了一個 getPromise函式,該函式接收兩個引數,一個參數列示網路請求的引數,另一個參數列示請求時間。該函式返回一個 Promise物件,在Promise物件中我們使用了setTimeout來模擬請求的延遲,根據傳入的timeout來決定延遲時間,延遲時間到達後會執行 resolve方法,將相關值回撥出來。

 

然後定義了一個Generator函式,在該函式中通過yield來呼叫每個函式,下方的Generator函式比較簡單,在此就不做過多贅述了。 

 

然後我們通過for -of 一次執行Generator函式的next方法,進而來執行每個getPromise方法。

  

下方是具體的執行結果,從執行結果中不難看出,每次獲取的yield值是一個Promise物件,我們可在該Promise物件的then方法中獲取到相關的結果值。從輸出順序中可以看出,會先輸出時間小的那個結果。

 

本篇部落格就先到這兒吧,下篇會聊一些saga的相關內容。

 

相關文章