【探祕ES6】系列專欄(十):更深刻了解生成器

ES6做爲新一代JavaScript標準,已正式與廣大前端開發者見面。爲了讓你們對ES6的諸多新特性有更深刻的瞭解,Mozilla Web開發者博客推出了《ES6 In Depth》系列文章。CSDN已獲受權,將持續對該系列進行翻譯,組織成【探祕ES6】系列專欄,供你們學習借鑑。本文爲該系列的第十篇。
前端

本文接下來繼續講述有關生成器的更多特性。es6

回顧算法

上一篇文章中,主要介紹了有關生成器(Generators)的基本用法。生成器函數與常規函數相似,其主要區別是生成器函數體不會一次所有運行。它會按部分來執行,當碰到yield表達式的時候會暫停執行。編程

例如:異步

function* someWords() {  
  yield "hello";  
  yield "world";  
}  
  
for (var word of someWords()) {  
  alert(word);  
}

接入來就生成器用法做進一步介紹。

如何關閉生成器async

生成器還有幾個特性須要介紹下:異步編程

  • generator.return()函數

  • the optional argument to generator.next()oop

  • generator.throw(error)學習

  • yield*

先看一個常規的cleanup程序:

function doThings() {  
  setup();  
  try {  
    // ... do some things ...  
  } finally {  
    cleanup();  
  }  
}  
  
doThings();

cleanup方法關閉的是鏈接或文件,釋放系統資源,或僅是更新DOM來關閉一個運行中的spinner。該方法用於檢視程序是否正常結束,因此在finally區塊調用。

換用生成器來寫會怎麼樣呢?

function* produceValues() {  
  setup();  
  try {  
    // ... yield some values ...  
  } finally {  
    cleanup();  
  }  
}  
  
for (var value of produceValues()) {  
  work(value);  
}

生成器寫法有個細微區別:work(value)的調用在區塊外進行。若是它拋出異常,對於cleanup會有影響嗎?或者說對於含有break或return語句的for-of循環,cleanup又是怎樣處理的?答案是ES6會繼續執行。

在以前討論迭代器(iterator)和for-of循環的時候,咱們知道迭代器接口含有由程序自動執行的可選方法.return。生成器也支持該方法。調用myGenerator.return()方法會使生成器執行任何finally區塊而後退出,其功能相似於yield轉入return語句。

要注意的是.return()的自動執行並非全部語言都支持的,只有符合迭代協議的語言才支持。因此生成器會在不執行finally區塊前被進行垃圾回收。其具體工做過程是:生成器在任務執行過程當中被凍結,而後進行一些設定操做。若是這時某點有異常拋出,for循環會捕獲它但不立刻處理,隨後使生成器執行.return()。待生成器執行完畢後並關閉後,for會繼續對以前的異常進行處理。

生成器管理

到目前爲止,對生成器的講述都是它自己,那麼對於用戶端而言,生成器還能作更多的事嗎?先看一個對話:


這裏用戶是命令者,但這不是惟一與生成器打交道的方式。接下來會再詳細講述有關生成器的異步編程。

先思考下若是.next()調用者向生成器傳回一個值會有什麼結果?這樣的變動會出現以下新的對話:


可見,yield與return的不一樣;yield表達式是有值的。

var results = yield getDataAndLatte(request.areaCode);

該特性能夠作不少事情:

  • 調用getDataAndLatte()。例如在上圖中返回字符串「get me the database records for area code...」

  • 暫停生成器,輸出字符串值;

  • 在這過程當中,程序是一直運行的;

  • 直到調用.next({data: ..., coffee: ...})。咱們把對象存入本地變量results而後繼續執行下一行代碼。

其完整代碼是:

function* handle(request) {  
  var results = yield getDataAndLatte(request.areaCode);  
  results.coffee.drink();  
  var target = mostUrgentRecord(results.data);  
  yield updateStatus(target.id, "ready");  
}

先重溫yield的功能:暫停生成器,向調用者回傳一個值。可是狀況出現了變化!這裏的生成器須要調用者承擔管理者的角色,這是對原函數的功能拓展。

那麼管理者角色到底是什麼呢?例如如下代碼:

function runGeneratorOnce(g, result) {  
  var status = g.next(result);  
  if (status.done) {  
    return;  // phew!  
  }  
  
  // The generator has asked us to fetch something and  
  // call it back when we're done.  
  doAsynchronousWorkIncludingEspressoMachineOperations(  
    status.value,  
    (error, nextResult) => runGeneratorOnce(g, nextResult));  
}

要使它正常運做,須要建立一個生成器並運行:
runGeneratorOnce(handle(request), undefined);

在以前的文章中,咱們說過Q.async()是一個示例庫可以使生成器執行異步操做並自動運行,.runGeneratorOnce與它相似。

事實上,生成器yield的對象是Promise對象,這須要學習並掌握,掌握好以後,異步算法的編寫將會變得容易。

異常處理

知道runGeneratorOnce會如何處理異常嗎?它會忽略它!

這不是最好的作法,生成器須要知道發生了什麼情況。具體作法是調用generator.throw(error)而不是generator.next(result)。這會使yield表達式進行異常拋出。相似於.return(),生成器會被銷燬,但若是當前的yield點處於try區塊,catch和finally區塊可幫助生成器進行異常恢復。

修改runGeneratorOnce來實現.throw()是另外一種不錯的策略。請記住由生成器拋出的異常都會被傳回調用者。因此generator.throw(error)會把異常拋回除非生成器主動捕獲了異常。

因此當生成器遇到yield表達式並暫停時,會出現以下的幾種狀況:

  • 若調用了generator.next(value),在這種狀況下,生成器會恢復到前次退出的位置;

  • 若調用了generator.next(value),選擇性地返回一個值。在這種狀況下,生成器不會進行恢復,而僅是執行finally區塊;

  • 若調用了generator.throw(error),生成器就會把yield表達式做爲函數回調看待並拋出異常;

  • 若是沒有進行任何調用,生成器便如同被永久凍結了。

讓生成器一塊兒工做

再舉多一個生成器函數例子,功能是鏈接兩個遍歷對象:

function* concat(iter1, iter2) {  
  for (var value of iter1) {  
    yield value;  
  }  
  for (var value of iter2) {  
    yield value;  
  }  
}

在ES6可簡寫爲:
function* concat(iter1, iter2) {  
  yield* iter1;  
  yield* iter2;  
}

普通yield表達式的輸出是單個值,一個yield*表達式會遍歷所有迭代器並輸出全部值。

相同的語法能幫助解決一個有趣的問題:如何在生成器內調用另外的生成器:

function* factoredOutChunkOfCode() { ... }  
  
function* refactoredFunction() {  
  ...  
  yield* factoredOutChunkOfCode();  
  ...  
}

寫在最後

關於生成器的講述到此結束,但願對讀者有所幫助。(譯者:伍昆,責編:陳秋歌)

原文連接:  ES6 In Depth: Generators, continued

本譯文遵循Creative Commons Attribution Share-Alike License v3.0 

相關文章
相關標籤/搜索