【问题标题】:What are ES6 generators and how can I use them in node.js?什么是 ES6 生成器,我如何在 node.js 中使用它们?
【发布时间】:2013-09-21 10:25:43
【问题描述】:

我今天参加了一个 node.js 聚会,我在那里遇到的人说 node.js 有 es6 生成器。他说这是对回调风格编程的巨大改进,并将改变节点格局。 Iirc,他说了一些关于调用堆栈和异常的事情。

我查找了它们,但还没有真正找到任何以适合初学者的方式解释它们的资源。生成器的高级概述是什么,与回调有何不同(或更好?)?

PS:如果您可以提供一段代码来突出显示常见场景(发出 http 请求或 db 调用)中的差异,那将非常有帮助。

【问题讨论】:

    标签: javascript node.js generator ecmascript-6


    【解决方案1】:

    生成器、纤程和协程

    “生成器”(除了“生成器”)也是"fibers" or "coroutines" 的基本构建块。使用纤程,您可以“暂停”等待异步调用返回的函数,有效避免“当场”声明回调函数并创建“闭包”。告别回调地狱。

    闭包和try-catch

    ...他说了一些关于调用堆栈和异常的事情

    “闭包”的问题在于,即使它们“神奇地”为回调保持局部变量的状态,“闭包”也无法保持调用堆栈。

    在回调的那一刻,通常调用函数已经返回很久了,所以调用函数上的任何“catch”块无法在异步函数本身或回调中捕获异常。这提出了一个大问题。因此,您不能将回调+闭包与异常捕获结合起来。

    等待

    ...并且会改变节点的格局

    如果你使用生成器来构建像Wait.for-ES6 这样的辅助库(我是作者),你可以完全避免回调和闭包,现在“catch blocks”按预期工作,而且代码很简单。

    如果您可以提供一段代码来突出显示常见场景(发出 http 请求或 db 调用)中的差异,那将非常有帮助。

    查看Wait.for-ES6 示例,查看带有回调和基于生成器的纤程的相同代码。


    2021 年更新:所有这些都已被 javascript/ES2020 async/await 取代。我的建议是使用 Typescript 和 async/await(基于 Promises 也是标准化的)

    【讨论】:

    • 赞成更新。
    【解决方案2】:

    生成器是即将推出的 ES6 中的many 功能之一。所以在the future 中将可以在浏览器中使用它们(现在你可以在FF 中使用它们)。

    生成器是迭代器的构造器。听起来像胡言乱语,所以用更简单的术语来说,它们允许创建对象,以后可以使用.next() 方法通过类似 for 循环的方式进行迭代。

    生成器的定义方式与函数类似。除非他们有*yield。 * 是告诉这是生成器,yield类似于return。

    例如这是一个生成器:

    function *seq(){
        var n = 0;
        while (true) yield n++;
    }
    

    然后您可以将此生成器与var s = seq() 一起使用。但与函数相比,它不会执行所有操作并为您提供结果,它只会实例化生成器。只有当您运行s.next() 时,才会执行生成器。这里的yield类似于return,但是当yield运行时,它会暂停生成器并在next之后继续处理下一个表达式。但是当下一个s.next() 被调用时,生成器将恢复执行。在这种情况下,它将永远继续执行 while 循环。

    所以你可以迭代这个

    for (var i = 0; i < 5; i++){
      console.log( s.next().value )
    }
    

    或使用特定的生成器构造:

    for (var n of seq()){
        if (n >=5) break;
        console.log(n);
    }
    

    这些是关于生成器的基础知识(您可以查看yield*next(with_params)throw() 和其他其他构造)。请注意,它是关于 ES6 中的生成器(因此您可以在节点和浏览器中完成所有这些操作)。

    但是这个无限的数列和回调有什么关系呢?

    这里重要的是 yield 会暂停生成器。所以想象一下你有一个非常奇怪的系统,它以这种方式工作:

    你有用户的数据库,你需要找到一个用户的名字和一些 ID,然后你需要在你的文件系统中检查这个用户名的密钥,然后你需要连接到一些具有用户 ID 的 ftp和键并在连接后做一些事情。 (听起来很荒谬,但我想展示嵌套回调)。

    以前你会这样写:

    var ID = 1;
    database.find({user : ID}, function(userInfo){
        fileSystem.find(userInfo.name, function(key){
            ftp.connect(ID, key, function(o){
                console.log('Finally '+o);
            })
        })
    });
    

    哪个是回调里面的回调里面的回调里面的回调。现在您可以编写如下内容:

    function *logic(ID){
      var userInfo  = yield database.find({user : ID});
      var key       = yield fileSystem.find(userInfo.name);
      var o         = yield ftp.connect(ID, key);
      console.log('Finally '+o);
    }
    var s = logic(1);
    

    然后使用它with s.next(); 如您所见,没有嵌套回调。

    因为 node 大量使用嵌套回调,这就是为什么那个家伙告诉生成器可以改变 node 的景观的原因。

    【讨论】:

    • 您能否确认您的yield 示例是正确的?回调示例似乎使用每个函数的返回值来调用下一个函数,并执行最终操作,但yield 示例似乎将三个值返回给调用者,而不仅仅是最后一个。看起来您正在使用yield,就好像它是await
    【解决方案3】:

    生成器是两个事物的组合 - IteratorObserver

    迭代器

    迭代器是在被调用时返回一个可迭代的东西,这是你可以迭代的东西。从 ES6 开始,所有集合(Array、Map、Set、WeakMap、WeakSet)都符合 Iterable 契约。

    生成器(迭代器)是生产者。在迭代中,消费者PULLs 来自生产者的值。

    例子:

    function *gen() { yield 5; yield 6; }
    let a = gen();
    

    每当您调用a.next() 时,您实际上是来自迭代器的pull-ing 值和pauseyield 处的执行。下次您调用a.next() 时,执行将从先前暂停的状态恢复。

    观察者

    生成器也是一个观察者,您可以使用它将一些值发送回生成器。通过示例更好地解释。

    function *gen() {
      document.write('<br>observer:', yield 1);
    }
    var a = gen();
    var i = a.next();
    while(!i.done) {
      document.write('<br>iterator:', i.value);
      i = a.next(100);
    }

    在这里你可以看到yield 1 的使用就像一个表达式,它计算出某个值。它计算的值是作为参数发送给a.next 函数调用的值。

    因此,i.value 将第一次成为第一个产生的值 (1),当继续迭代到下一个状态时,我们使用 a.next(100) 将值发送回生成器。

    你可以在 Node.JS 的什么地方使用它?

    生成器广泛用于spawn(来自taskJS 或co)函数,该函数接收一个生成器并允许我们以同步方式编写异步代码。这并不意味着异步代码被转换为同步代码/同步执行。这意味着我们可以编写看起来像 sync 但内部仍然是 async 的代码。

    同步正在阻塞;异步正在等待。编写阻塞的代码很容易。 PULLing 时,赋值位置出现值。 PUSHing时,回调的参数位置出现value

    当你使用迭代器时,你PULL 来自生产者的值。使用回调时,生产者PUSHes 将值传递给回调的参数位置。

    var i = a.next() // PULL
    dosomething(..., v => {...}) // PUSH
    

    在这里,您从a.next() 中提取值,然后,v =&gt; {...} 是回调函数,一个值被PUSHed 到回调函数的参数位置v

    使用这种pull-push机制,我们可以这样写异步编程,

    let delay = t => new Promise(r => setTimeout(r, t));
    spawn(function*() {
      // wait for 100 ms and send 1
      let x = yield delay(100).then(() => 1);
      console.log(x); // 1
    
       // wait for 100 ms and send 2
      let y = yield delay(100).then(() => 2);
      console.log(y); // 2
    });
    

    所以,看看上面的代码,我们正在编写看起来像blocking 的异步代码(yield 语句等待 100 毫秒然后继续执行),但实际上它是 waiting。生成器的pauseresume 属性使我们能够做到这一点。

    它是如何工作的?

    spawn 函数使用yield promise 从生成器中提取承诺状态,等待承诺解决,然后将解决的值推送回生成器,以便它可以使用它。

    立即使用

    因此,使用生成器和 spawn 函数,您可以清理 NodeJS 中的所有异步代码,使其看起来和感觉上是同步的。这将使调试变得容易。代码看起来也很整洁。

    顺便说一句,这是 ES2017 原生的 JavaScript - 作为 async...await。但是您现在可以使用库中定义的 spawn 函数在 ES2015/ES6 和 ES2016 中使用它们 - taskjs、co 或 bluebird

    【讨论】:

      【解决方案4】:

      总结:

      function* 定义了一个生成器函数,它返回一个生成器对象。生成器函数的特殊之处在于它在使用() 运算符调用时不会执行。而是返回一个迭代器对象。

      这个迭代器包含一个next() 方法。迭代器的 next() 方法返回一个对象,该对象包含一个 value 属性,该属性包含产生的值。 yield 返回的对象的第二个属性是 done 属性,它是一个 boolean(如果生成器函数完成,它应该返回 true)。

      示例:

      function* IDgenerator() {
        var index = 0;
      
        yield index++;
        yield index++;
        yield index++;
        yield index++;
          
      }
      
      var gen = IDgenerator(); // generates an iterator object
      
      console.log(gen.next().value); // 0  
      console.log(gen.next().value); // 1
      console.log(gen.next().value); // 2
      console.log(gen.next()); // object, 
      console.log(gen.next()); // object done

      在这个例子中,我们首先生成一个迭代器对象。在这个迭代器对象上,我们可以调用next() 方法,它允许我们从yield 跳转到yield 值。我们返回一个对象,它既有值又有done 属性。

      这有什么用?

      • 一些库和框架可能会使用这个结构来等待异步代码的完成,例如redux-saga
      • async await 让您等待 async 事件的新语法在后台使用了它。了解生成器的工作原理将使您更好地了解此构造的工作原理。

      【讨论】:

        【解决方案5】:

        要在 node 中使用 ES6 生成器,您需要安装 node >= 0.11.2iojs

        在节点中,您需要引用和声标志:

        $ node --harmony app.js 
        

        或者你可以明确地引用生成器标志

        $ node --harmony_generators app.js
        

        如果你已经安装了iojs,你可以省略和谐标志。

        $ iojs app.js
        

        有关如何使用生成器的高级概述,checkout this post

        【讨论】:

          猜你喜欢
          • 2015-02-15
          • 2014-06-30
          • 1970-01-01
          • 1970-01-01
          • 2011-10-08
          • 2022-08-13
          • 2017-12-03
          • 2013-08-11
          • 2015-04-05
          相关资源
          最近更新 更多