什么是ES6生成器以及如何在node.js中使用它们?

时间:2013-09-17 05:45:08

标签: javascript node.js generator ecmascript-6

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

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

PS:如果你能提供一段代码来强调常见场景的差异(发出http请求或db调用),那将非常有用。

5 个答案:

答案 0 :(得分:14)

发电机,光纤和协同程序

“发电机”(除了是“发电机”)也是"fibers" or "coroutines"的基本建筑物。使用光纤,您可以“暂停”等待异步调用返回的函数,从而有效地避免在“现场”声明回调函数并创建“闭包”。告别回电地狱吧。

闭包和试听

  

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

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

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

Wait.for

  

...并会更改节点格局

如果你使用生成器构建一个帮助库,如Wait.for-ES6(我是作者),你可以完全避免回调和闭包,现在“抓住块”工作正如预期的那样,代码很简单。

  

如果您可以提供一段代码来突出显示常见方案(发出http请求或数据库调用)的差异,那将非常有用。

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

答案 1 :(得分:9)

Generators是即将推出的ES6中的many功能之一。因此,在the future中,可以在浏览器中使用它们(现在您可以在FF中使用它们)。

生成器是迭代器的构造函数。听起来像是胡言乱语,所以从容易的角度来说,它们允许创建对象,以后可以使用.next()方法迭代类似for循环。

生成器的定义方式与函数类似。除了他们中有*yield。 *是告诉这是发电机,收益率类似于返还。

例如,这是一个生成器:

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

然后您可以将此生成器与var s = seq()一起使用。但是与一个函数相反,它不会执行所有操作并给你一个结果,它只会实例化生成器。只有当您运行s.next()时,才会执行生成器。这里yield与return类似,但是当yield将运行时,它将暂停生成器并继续处理下一个表达式。但是当调用下一个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中的生成器(因此您可以在节点和浏览器中执行所有这些操作)。

但这个无限数字序列与回调有什么关系?

重要的是,产量会暂停发电机。所以想象一下你有一个非常奇怪的系统以这种方式工作:

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

以前你会写这样的东西:

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();如您所见,没有嵌套回调。

因为节点大量使用嵌套回调,所以这就是为什么这个人告诉生成器可以改变节点的格局。

答案 2 :(得分:8)

生成器是两件事的组合 - IteratorObserver

迭代

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

  

生成器(迭代器)是生产者。在迭代中,消费者PULL是生产者的价值。

示例:

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

每当您致电a.next()时,您实际上是pull来自Iterator的值,pause执行yield。下次调用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),并且当继续迭代到下一个状态时,我们使用{{1}将值发送回生成器}}

你在哪里可以在Node.JS中使用它?

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

  

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

当您使用迭代器时,您async来自生产者的值。当你使用回调时,生成器PULL将值作为回调的参数位置。

PUSH

在这里,您从var i = a.next() // PULL dosomething(..., v => {...}) // PUSH 中提取值,在第二个中,a.next()是回调,值v => {...}编入回调的参数位置PUSH功能

使用这种推拉机制,我们可以编写像这样的异步编程,

v

所以,看看上面的代码,我们编写的异步代码看起来像是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 }); (yield语句等待100ms,然后继续执行),但它实际上是blocking。生成器的waitingpause属性允许我们执行这个惊人的技巧。

它是如何运作的?

spawn函数使用resume从生成器中提取promise类,等待promise被解析,然后将解析后的值推回给生成器,以便它可以使用它。

立即使用

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

顺便说一句,这是针对ES2017本身的JavaScript - yield promise。但是你现在可以使用库中定义的spawn函数在ES2015 / ES6和ES2016中使用它们 - taskjs,co或bluebird

答案 3 :(得分:1)

摘要:

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

此迭代器包含一个next()方法。迭代器的next()方法返回一个对象,该对象包含一个value属性,该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事件在后台使用。了解生成器的工作方式将使您对这种构造的工作方式有更好的了解。

答案 4 :(得分:0)

要在节点中使用ES6生成器,您需要安装node&gt; = 0.11.2iojs

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

$ node --harmony app.js 

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

$ node --harmony_generators app.js

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

$ iojs app.js

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