ES6作為新一代JavaScript標準,已正式與廣大前端開發者見面。為了讓大家對ES6的諸多新特性有更深入的了解,Mozilla Web開發者博客推出了《ES6 In Depth》系列文章。CSDN已獲授權,將持續對該系列進行翻譯,組織成【探秘ES6】系列專欄,供大家學習借鑒。本文為該系列的第十篇。
本文接下來繼續講述有關生成器的更多特性。
回顧
在上一篇文章中,主要介紹了有關生成器(Generators)的基本用法。生成器函數與常規函數類似,其主要區別是生成器函數體不會一次全部運行。它會按部分來執行,當碰到yield表達式的時候會暫停執行。
例如:
[js] view plaincopy
function* someWords() {
yield "hello";
yield "world";
}
for (var word of someWords()) {
alert(word);
}
接入來就生成器用法作進一步介紹。
如何關閉生成器
生成器還有幾個特性需要介紹下:
generator.return()
the optional argument to generator.next()
generator.throw(error)
yield*
先看一個常規的cleanup程序:
[js] view plaincopy
function doThings() {
setup();
try {
// ... do some things ...
} finally {
cleanup();
}
}
doThings();
cleanup方法關閉的是連接或文件,釋放系統資源,或僅是更新DOM來關閉一個運行中的spinner。該方法用于檢視程序是否正常結束,所以在finally區塊調用。
換用生成器來寫會怎么樣呢?
[js] view plaincopy
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表達式是有值的。
[js] view plaincopy
var results = yield getDataAndLatte(request.areaCode);
該特性可以做很多事情:
調用getDataAndLatte()。例如在上圖中返回字符串“get me the database records for area code...”
暫停生成器,輸出字符串值;
在這過程中,程序是一直運行的;
直到調用.next({data: ..., coffee: ...})。我們把對象存入本地變量results然后繼續執行下一行代碼。
其完整代碼是:
[js] view plaincopy
function* handle(request) {
var results = yield getDataAndLatte(request.areaCode);
results.coffee.drink();
var target = mostUrgentRecord(results.data);
yield updateStatus(target.id, "ready");
}
先重溫yield的功能:暫停生成器,向調用者回傳一個值。但是情況出現了變化!這里的生成器需要調用者承擔管理者的角色,這是對原函數的功能拓展。
那么管理者角色究是什么呢?例如以下代碼:
[js] view plaincopy
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));
}
要使它正常運作,需要創建一個生成器并運行:
[js] view plaincopy
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表達式作為函數回調看待并拋出異常;
如果沒有進行任何調用,生成器便如同被永久凍結了。
讓生成器一起工作
再舉多一個生成器函數例子,功能是連接兩個遍歷對象:
[js] view plaincopy
function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}
在ES6可簡寫為:
[js] view plaincopy
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}
普通yield表達式的輸出是單個值,一個yield*表達式會遍歷全部迭代器并輸出所有值。
相同的語法能幫助解決一個有趣的問題:如何在生成器內調用另外的生成器:
[js] view plaincopy
function* factoredOutChunkOfCode() { ... }
function* refactoredFunction() {
...
yield* factoredOutChunkOfCode();
...
}
寫在后
關于生成器的講述到此結束,希望對讀者有所幫助。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。