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