2015-02-08 99 views
3

在閱讀javascript博客和文章時,我看到了很多ES6生成器的興趣,但是我沒有理解它們在本質上與當前使用一系列函數創建的序列的差異。例如,下面的工廠將採取一系列功能步驟並在步驟之間產生。ES6生成器和函數數組之間的區別

function fakeGen(funcList) { 
    var i = 0, context; 
    return function next() { 
     if (i<funcList.lenght) { 
      return {value: funcList[i++](context)} 
     } else return {done:true} 
    } 
} 

我錯過了什麼好處,以及轉發器如何在ES6中實現魔術?

+0

它更多的是你從代碼中看不到的東西,而不是你所做的;內部優化。 – dandavis 2015-02-08 00:59:20

回答

2

@tophallen是對的。您可以完全在ES3/ES5中實現相同的功能。但語法不盡相同。我們舉一個例子,希望解釋爲什麼語法很重要。

ES6生成器的主要應用之一是異步操作。有severalrunners被設計來包裝發電機產生的序列Promises。當包裹的發生器產生承諾時,這些跑步者等待,直到該承諾解決或被拒絕,然後恢復生成器,將結果傳回或在使用iterator.throw()的屈服點拋出異常。

一些跑步者,如tj/co,另外允許生成承諾數組,傳回數組值。

這裏是例子。這個函數執行兩個並行的URL請求,然後解析它們的結果如JSON,組合它們以某種方式,組合數據發送至其他URL,並返回回答(一個的承諾):

var createSmth = co.wrap(function*(id) { 
    var results = yield [ 
    request.get('http://some.url/' + id), 
    request.get('http://other.url/' + id) 
    ]; 
    var jsons = results.map(JSON.parse), 
     entity = { x: jsons[0].meta, y: jsons[1].data }; 
    var answer = yield request.post('http://third.url/' + id, JSON.stringify(entity)); 
    return { entity: entity, answer: JSON.parse(answer) }; 
}); 

createSmth('123').then(consumeResult).catch(handleError); 

注意,這個代碼包含幾乎沒有樣板。大多數行執行上面描述中存在的一些操作。

還注意到缺少錯誤處理代碼。同步(如JSON解析錯誤)和異步(如失敗的url請求)的所有錯誤都會自動處理,並會拒絕所產生的承諾。

如果您需要從某些錯誤中恢復(即阻止它們拒絕生成的Promise),或者使它們更具體,那麼您可以將發生器中的任何代碼塊都包圍在try..catch之內,並且同步和異步錯誤將在catch區塊結束。

同樣可以使用的功能的陣列和一些輔助庫像async可以肯定實現:

var createSmth = function(id, cb) { 
    var entity; 
    async.series([ 
    function(cb) { 
     async.parallel([ 
     function(cb){ request.get('http://some.url/' + id, cb) }, 
     function(cb){ request.get('http://other.url/' + id, cb) } 
     ], cb); 
    }, 
    function(results, cb) { 
     var jsons = results.map(JSON.parse); 
     entity = { x: jsons[0].meta, y: jsons[1].data }; 
     request.post('http://third.url/' + id, JSON.stringify(entity), cb); 
    }, 
    function(answer, cb) { 
     cb(null, { entity: entity, answer: JSON.parse(answer) }); 
    } 
    ], cb); 
}; 

createSmth('123', function(err, answer) { 
    if (err) 
    return handleError(err); 
    consumeResult(answer); 
}); 

但是,這實在是太醜了。更好的想法是使用承諾:

var createSmth = function(id) { 
    var entity; 
    return Promise.all([ 
    request.get('http://some.url/' + id), 
    request.get('http://other.url/' + id) 
    ]) 
    .then(function(results) { 
    var jsons = results.map(JSON.parse); 
    entity = { x: jsons[0].meta, y: jsons[1].data }; 
    return request.post('http://third.url/' + id, JSON.stringify(entity)); 
    }) 
    .then(function(answer) { 
    return { entity: entity, answer: JSON.parse(answer) }; 
    }); 
}; 

createSmth('123').then(consumeResult).catch(handleError); 

短,更清潔,但仍比代碼在使用發電機的版本。還有一些樣板代碼。注意這些.then(function(...) {行和var entity聲明:它們不執行任何有意義的操作。

較少的樣板(=生成器)使您的代碼更容易理解和修改,寫得更有樂趣。這些是任何代碼最重要的特徵之一。這就是爲什麼很多人,尤其是那些習慣了其他語言的類似概念的人,對發電機非常欣喜若狂:)

關於第二個問題:transpilers使用閉包,switch語句和狀態對象來做他們的沉浸魔法。例如,該功能:

function* f() { 
    var a = yield 'x'; 
    var b = yield 'y'; 
} 

regenerator轉化爲這一個(的Traceur輸出看起來非常相似):

var f = regeneratorRuntime.mark(function f() { 
    var a, b; 
    return regeneratorRuntime.wrap(function f$(context$1$0) { 
    while (1) switch (context$1$0.prev = context$1$0.next) { 
     case 0: 
     context$1$0.next = 2; 
     return "x"; 
     case 2: 
     a = context$1$0.sent; 
     context$1$0.next = 5; 
     return "y"; 
     case 5: 
     b = context$1$0.sent; 
     case 6: 
     case "end": 
     return context$1$0.stop(); 
    } 
    }, f, this); 
}); 

正如你所看到的,沒有什麼神奇的是,得到ES5是相當微不足道的。真正的魔力在於生成ES5的代碼,即在轉譯代碼中,因爲它們需要支持所有可能的邊緣情況。最好這樣做的結果是高性能的輸出代碼。

UPDhere is an interesting article可以追溯到2000年,並描述在純C :)是再生器和其他ES6> ES5 transpilers用於捕獲發電機的狀態的技術實現僞協同程序非常相似。

+0

感謝您的詳細解答。我從中學到的東西是錯誤處理和更清晰的語法。 – Hurelu 2015-02-08 06:12:37

+0

是的,但這是很多:) – skozin 2015-02-08 18:51:07

+0

@Hurelu,我正在瀏覽我的書籤,並找到與您的第二個問題有關的文章。添加到答案中(請參閱底部的UPD)。 – skozin 2015-02-14 16:30:16

2

一個生成器本質上是一個枚舉函數,它允許在你調用它的時候改變你正在操作的上下文,真的和它和你的函數數組沒有太大的差別,但是你的優點得到的是它不一定是被評估的函數內部的函數,簡化了閉包。看看下面的例子:

function* myGenerator() { 
    for (var i = 0; i < arr.length; i++) { 
     yield arr[i]; 
    } 
} 

這是一個很簡單的例子,但不具有建立你需要提供回有人列舉結果的背景下,它提供給你,你可以確信,他們的在完成之前,done屬性將爲假。這個函數看起來比你給出的例子要乾淨得多。可能最大的優勢在於圍繞此優化的優化可能會發生,所以對象內存佔用空間得到優化。

一個很好的接觸是你列舉像這樣物體的多個集合時真的清理代碼:

function* myGenerator() { 
    for (var i = 0; i < arr.length; i++) { 
     yield arr[i]; 
    } 
    for (var i = 0; i < arr2.length; i++) { 
     yield arr2[i]; 
    } 
    yield* myGenerator2(); 
} 

這樣做,只有鏈接嵌套函數可以完成同樣的事情,但的可維護性和可讀性代碼有些受損。

至於transpilers去,從CS線:

沒有衝突。 Coffeescript只會生成所需的任何javascript,以便編譯它使用的任何語法,無論是舊的還是新的。
在過去,在所有瀏覽器都支持它之前,coffeescript不會使用任何JavaScript功能。這也可能適用於發電機。在此之前,您需要使用反引號。

我對大多數轉譯者的一般理解是,他們在實現不會遍歷回來並且通常兼容的功能時必須小心,因此通常會遲到。

就像你說的,一個生成器沒有做任何超特殊的事情,它只是使編碼更易於閱讀,維護,消耗或執行更好的語法糖。

+0

謝謝。另一個來自[sam.kozin](http:// stackoverflow。com/users/804678/sam-kozin)是錯誤處理部分。仍然讓我的頭在附近。 – Hurelu 2015-02-08 06:15:45

相關問題