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

tld*_*ldr 19 javascript generator node.js ecmascript-6

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

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

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

Luc*_*ato 14

发电机,纤维和协同程序

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

关闭和尝试捕获

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

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

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

等待

...并将改变节点格局

如果你使用生成器构建一个帮助器库,如Wait.for-ES6(我是作者),你可以完全避免回调和闭包,现在"catch blocks"按预期工作,代码很简单.

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

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


Sal*_*ali 9

Generators是即将推出的ES6中的众多功能之一.所以将来可以在浏览器中使用它们(现在你可以在FF中使用它们).

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

生成器的定义方式与函数类似.除了他们*yield他们之外.*是告诉这是发电机,产量类似于回报.

例如,这是一个发电机:

function *seq(){
    var n = 0;
    while (true) yield n++;
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用这个发电机var s = seq().但是与一个函数相反,它不会执行所有操作并给你一个结果,它只会实例化生成器.只有在你运行s.next()发电机时才会执行.这里yield与return类似,但是当yield将运行时,它将暂停生成器并继续处理下一个表达式.但是当s.next()调用下一个时,生成器将恢复执行.在这种情况下,它将继续执行while循环.

所以你可以用它来迭代

for (var i = 0; i < 5; i++){
  console.log( s.next().value )
}
Run Code Online (Sandbox Code Playgroud)

或者使用特定的发电机构造:

for (var n of seq()){
    if (n >=5) break;
    console.log(n);
}
Run Code Online (Sandbox Code Playgroud)

这是关于发电机的基础知识(你可以看看yield*,next(with_params),throw()和其他额外的结构).请注意,它是关于ES6中的生成器(因此您可以在节点和浏览器中执行所有这些操作).

但这个无限数字序列如何与回调有关?

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

您有用户的数据库,您需要找到具有某个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);
        })
    })
});
Run Code Online (Sandbox Code Playgroud)

哪个是回调内部回调内部回调内部的回调.现在你可以写下这样的东西:

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);
Run Code Online (Sandbox Code Playgroud)

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

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


Boo*_*jaa 8

发电机是两件事的组合 - 一个Iterator和一个Observer.

迭代器

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

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

例:

function *gen() { yield 5; yield 6; }
let a = gen();
Run Code Online (Sandbox Code Playgroud)

每当你打电话时a.next(),你实际上是pull从迭代器和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);
}
Run Code Online (Sandbox Code Playgroud)

在这里,您可以看到它yield 1的使用方式类似于计算某个值的表达式.它评估的值是作为a.next函数调用的参数发送的值.

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

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

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

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

使用迭代器时,您PULL将获得生产者的值.当您使用回调时,生产者PUSH将值重新设置为回调的参数位置.

var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Run Code Online (Sandbox Code Playgroud)

在这里,您从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
});
Run Code Online (Sandbox Code Playgroud)

因此,看看上面的代码,我们正在编写看起来像它的异步代码blocking(yield语句等待100ms然后继续执行),但实际上是这样waiting.在pauseresume发电机的性能可以让我们在这个惊人的伎俩.

它是如何工作的 ?

spawn函数用于yield promise从生成器中拉出promise状态,等待promise被解析,并将解析后的值推回给生成器,以便它可以使用它.

立即使用它

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

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