簡體   English   中英

ES6生成器與函數數組的區別

[英]Difference between ES6 Generators and Array of Functions

在閱讀javascript博客和文章時,我看到了很多人對ES6 Generators感興趣,但我不明白它們與使用一系列函數構成的當前序列的本質區別。 例如,下面的工廠將采取一系列功能步驟並在步驟之間產生。

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

我錯過了什么好處以及如何在ES6中實現魔法轉換?

生成器本質上是一個枚舉器函數,它允許在調用它時更改您正在操作的上下文,實際上它與您的函數數組之間沒有太大的區別,但是您獲得的優勢是它不必是正在評估的函數內的函數,簡化了閉包。 請看以下示例:

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

這是一個非常簡單的示例,但不是必須構建您需要為某人枚舉結果提供的上下文,它是為您提供的,並確保在done之前done屬性將為false。 此功能看起來比您給出的示例更清晰。 可能最大的優勢在於圍繞此優化可以在引擎蓋下進行,因此優化了對象內存占用。

一個很好的接觸是,在枚舉多個對象集合時,您真正清理代碼,如下所示:

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();
}

使用鏈接的嵌套函數執行此操作可以完成相同的任務,但代碼的可維護性和可讀性會受到一些影響。

就轉發器而言,來自CS線程:

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

我對大多數編譯器的一般理解是,在實現不會遍歷並且通常兼容的功能時,他們必須要小心,因此通常在游戲后期。

就像你說的那樣,生成器沒有做任何超級特殊的事情,它只是語法糖,使編碼更容易閱讀,維護,消費或表現更好。

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

ES6生成器的主要應用之一是異步操作。 幾個 跑步者設計用於包裹生成一系列Promise的生成器。 當一個包裝的生成器產生一個promise時,這些runners會等到Promise被解析或拒絕,然后恢復生成器,傳回結果或使用iterator.throw()在yield點拋出一個異常。

一些運行器,如tj / co ,還允許產生promises數組,傳回值數組。

這是一個例子。 此函數並行執行兩個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(...) { lines和var entity聲明:它們不執行任何有意義的操作。

較少的樣板(=生成器)使您的代碼更易於理解和修改,並且編寫起來更有趣。 這些是任何代碼最重要的特征之一。 這就是為什么許多人,特別是那些習慣於其他語言中的類似概念的人,對發電機如此欣喜若狂:)

關於你的第二個問題:轉發器使用閉包, switch語句和狀態對象來完成他們的魔術。 例如,這個功能:

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

將由再生器轉換為這個( 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的代碼,即在轉換器的代碼中,因為它們需要支持所有可能的邊緣情況。 並且最好以產生高性能輸出代碼的方式執行此操作。

UPD這是一篇有趣的文章 ,可以追溯到2000年,描述了在純C中偽協同程序的實現:) Regenerator和其他ES6> ES5轉換器用於捕獲生成器狀態的技術非常相似。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM