ES6生成器和功能数组之间的区别

胡雷鲁

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

通过

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

ES6生成器的主要应用之一是异步操作。几个 选手设计的包装,其产生的序列发生器的承诺当包装的生成器产生承诺时,这些运行程序将等待直到Promise被解决或被拒绝,然后再恢复生成器,将结果传回或使用导致在生成点抛出异常iterator.throw()

一些竞争者,例如tj / co,还允许产生promise数组,将值数组传回。

这是示例。此函数并行执行两个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声明:它们不执行任何有意义的操作。

更少的样板代码(= generators)使您的代码更易于理解和修改,并且编写起来更加有趣。这些是任何代码的最重要特征之一。这就是为什么许多人,尤其是那些习惯于其他语言的类似概念的人,对生成器如此着迷的原因:)

关于您的第二个问题:编译器使用闭包,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编译器用来捕获生成器状态的技术非常相似。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章