简体   繁体   English

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

[英]Difference between ES6 Generators and Array of Functions

When reading javascript blogs and articles I see a lot of interest in ES6 Generators but I fail to understand how they differ in essence to a current sequence made with an array of functions. 在阅读javascript博客和文章时,我看到了很多人对ES6 Generators感兴趣,但我不明白它们与使用一系列函数构成的当前序列的本质区别。 For example, the factory below would take an array of funtion steps and yield between steps. 例如,下面的工厂将采取一系列功能步骤并在步骤之间产生。

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

What benefit am I missing and how do transpilers implement the magic in ES6? 我错过了什么好处以及如何在ES6中实现魔法转换?

A generator is essentially an enumerator function, it allows for the context you are operating against to be changed while you are calling it, really there isn't a huge difference between it and your array of functions, however the advantage you get is that it doesn't have to be functions inside the functions that are being evaluated, simplifying closures. 生成器本质上是一个枚举器函数,它允许在调用它时更改您正在操作的上下文,实际上它与您的函数数组之间没有太大的区别,但是您获得的优势是它不必是正在评估的函数内的函数,简化了闭包。 Take the following example: 请看以下示例:

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

it is a very simple example, but rather than having to build the context you need to provide back for someone to enumerate the results, it's provided for you, and you are ensured that the done property will be false until it is complete. 这是一个非常简单的示例,但不是必须构建您需要为某人枚举结果提供的上下文,它是为您提供的,并确保在done之前done属性将为false。 This function looks a lot cleaner than the example you gave. 此功能看起来比您给出的示例更清晰。 Probably the biggest advantage is that the optimization around this can happen under the hood, so the object memory footprint is optimized. 可能最大的优势在于围绕此优化可以在引擎盖下进行,因此优化了对象内存占用。

A nice touch is that your really clean up the code when enumerating multiple collections of objects like so: 一个很好的接触是,在枚举多个对象集合时,您真正清理代码,如下所示:

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

doing that with just chained nested functions can accomplish the same thing, but the maintainability and readability of the code suffers somewhat. 使用链接的嵌套函数执行此操作可以完成相同的任务,但代码的可维护性和可读性会受到一些影响。

As far as transpilers go, from a CS thread: 就转发器而言,来自CS线程:

There is no conflict. 没有冲突。 Coffeescript will just generate whatever javascript it needs to compile whatever syntax it uses, old or new. Coffeescript将生成编译它使用的任何语法所需的任何javascript,无论是旧的还是新的。
In the past coffeescript would not use any javascript feature until all browsers support it. 过去,在所有浏览器都支持之前,coffeescript不会使用任何javascript功能。 This will probably apply to generators also. 这也可能适用于发电机。 Until then you will need to use backticks. 在此之前,您需要使用反引号。

My general understanding of most transpilers is that they have to be careful when implementing functionality that won't traverse back and be generally compatible, and as such are usually late to the game. 我对大多数编译器的一般理解是,在实现不会遍历并且通常兼容的功能时,他们必须要小心,因此通常在游戏后期。

Like you said, a generator isn't doing anything super special, it's just syntactic sugar that makes coding easier to read, maintain, consume, or performs better. 就像你说的那样,生成器没有做任何超级特殊的事情,它只是语法糖,使编码更容易阅读,维护,消费或表现更好。

@tophallen is right. @tophallen是对的。 You can implement the same functionality entirely in ES3/ES5. 您可以完全在ES3 / ES5中实现相同的功能。 But not the same syntax. 但语法不一样。 Let's take an example which will hopefully explain why the syntax matters. 让我们举一个例子,希望能解释为什么语法很重要。

One of the main applications of ES6 generators is asynchronous operations. ES6生成器的主要应用之一是异步操作。 There are several runners designed to wrap generators which produce a sequence of Promises . 几个 跑步者设计用于包裹生成一系列Promise的生成器。 When a wrapped generator yields a promise, these runners wait until that Promise is resolved or rejected, and then resume the generator, passing the result back or throwing an exception at the yield point using iterator.throw() . 当一个包装的生成器产生一个promise时,这些runners会等到Promise被解析或拒绝,然后恢复生成器,传回结果或使用iterator.throw()在yield点抛出一个异常。

Some runners, like tj/co , additionally allow to yield arrays of promises, passing back arrays of values. 一些运行器,如tj / co ,还允许产生promises数组,传回值数组。

And here is the example. 这是一个例子。 This function performs two url requests in parallel, then parses their results as JSON, combines them somehow, sends combined data to other url, and returns the (promise of an) answer: 此函数并行执行两个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);

Notice that this code contains almost no boilerplate. 请注意,此代码几乎不包含样板。 Most of the lines perform some action that exists in the description above. 大多数行执行上述描述中存在的某些操作。

Also notice the lack of error handling code. 另请注意缺少错误处理代码。 All errors, both synchronous (like JSON parsing errors) and asynchronous (like failed url requests) are handled automatically and will reject the resulting promise. 所有错误,包括同步(如JSON解析错误)和异步(如失败的url请求)都会自动处理,并拒绝生成的承诺。

If you need to recover from some errors (ie prevent them from rejecting the resulting Promise), or make them more specific, then you can surround any block of code inside a generator with a try..catch , and both sync and async errors will end up in the catch block. 如果您需要从某些错误中恢复(即阻止它们拒绝生成的Promise),或者使它们更具体,那么您可以使用try..catch包围生成器内的任何代码块,并且同步和异步错误都将最终进入了catch区。

The same can be definitely implemented using an array of functions and some helper library like async : 使用函数数组和一些辅助库(如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);
});

But that is really ugly. 但这真的很难看。 The better idea is to use promises: 更好的想法是使用承诺:

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

Shorter, cleaner, but still more code than in the version that uses generators. 与使用生成器的版本相比,更短,更干净,但代码更多。 And still some boilerplate code. 还有一些样板代码。 Notice these .then(function(...) { lines and var entity declaration: they do not perform any meaningful operation. 注意这些.then(function(...) { lines和var entity声明:它们不执行任何有意义的操作。

Less boilerplate (=generators) makes your code easier to understand and modify, and much more fun to write. 较少的样板(=生成器)使您的代码更易于理解和修改,并且编写起来更有趣。 And these are ones of the most important characteristics of any code. 这些是任何代码最重要的特征之一。 That's why many people, especially those who got used to similar concepts in other languages, are so ecstatic about generators :) 这就是为什么许多人,特别是那些习惯于其他语言中的类似概念的人,对发电机如此欣喜若狂:)

Regarding your second question: transpilers do their thanspiling magic using closures, switch statements and state objects. 关于你的第二个问题:转发器使用闭包, switch语句和状态对象来完成他们的魔术。 For example, this function: 例如,这个功能:

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

will be transformed by regenerator into this one (the output of Traceur looks very similar): 将由再生器转换为这个( 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);
});

As you can see, nothing magical here, the resulting ES5 is rather trivial. 正如你所看到的,这里没什么神奇之处,最终的ES5相当微不足道。 The real magic is in the code that generates that resulting ES5, ie in the code of transpilers, because they need to support all possible edge cases. 真正的魔力在于生成ES5的代码,即在转换器的代码中,因为它们需要支持所有可能的边缘情况。 And preferably do this in a way that results in performant output code. 并且最好以产生高性能输出代码的方式执行此操作。

UPD : here is an interesting article that dates back to 2000 and describes implementation of pseudo-coroutines in plain C :) The technique that Regenerator and other ES6 > ES5 transpilers use to capture generator's state is very similar. UPD这是一篇有趣的文章 ,可以追溯到2000年,描述了在纯C中伪协同程序的实现:) Regenerator和其他ES6> ES5转换器用于捕获生成器状态的技术非常相似。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM