ES6生成器与函数数组的区别

时间:2015-02-08 00:49:48

标签: javascript sequence-generators

在阅读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中实现魔法转换?

2 个答案:

答案 0 :(得分:2)

生成器本质上是一个枚举器函数,它允许在调用它时更改您正在操作的上下文,实际上它与您的函数数组之间没有太大的区别,但是你的优点是get是它不必是被评估函数内部的函数,简化了闭包。请看以下示例:

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

这是一个非常简单的示例,但不是必须构建您需要提供的上下文,以便为某人列举结果,而是为您提供,并确保您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功能。这也可能适用于发电机。在此之前,您需要使用反引号。

我对大多数转录程序的一般理解是,在实现不会遍历并且通常兼容的功能时,他们必须要小心,因此通常在游戏后期。

就像你说的那样,生成器没有做任何超级特殊的事情,它只是语法糖,使编码更容易阅读,维护,消费或表现更好。

答案 1 :(得分:2)

@tophallen是对的。您可以完全在ES3 / ES5中实现相同的功能。但语法不一样。让我们举一个例子,希望能解释为什么语法很重要。

ES6生成器的主要应用之一是异步操作。有several runners用于包装生成Promises序列的生成器。当一个包装的生成器产生一个promise时,这些runners会等到Promise被解析或拒绝,然后恢复生成器,传回结果或使用iterator.throw()在yield点抛出异常。

一些跑步者,比如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声明:它们不执行任何有意义的操作。

较少的样板(=生成器)使您的代码更易于理解和修改,并且编写起来更有趣。这些是任何代码最重要的特征之一。这就是为什么许多人,特别是习惯于其他语言的类似概念的人,对发电机如此欣喜若狂:)

关于你的第二个问题:转发器使用闭包,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的代码,即在转换器的代码中,因为它们需要支持所有可能的边缘情况。并且最好以产生高性能输出代码的方式执行此操作。

UPD here is an interesting article可追溯到2000年,描述了普通C中伪协同程序的实现:) Regenerator和其他ES6的技术&gt; ES5转发器用于捕获发生器的状态非常相似。