我今天参加了一个node.js聚会,我在那里遇到的人说node.js有es6生成器。他说,这是对回调式编程的巨大改进,并将改变节点格局。 Iirc,他说了一些关于调用堆栈和异常的事情。
我查了一下,但还没有找到任何以初学者友好的方式解释它们的资源。什么是生成器的高级概述,与回调的不同(或更好?)是什么?
PS:如果你能提供一段代码来强调常见场景的差异(发出http请求或db调用),那将非常有用。
答案 0 :(得分:14)
“发电机”(除了是“发电机”)也是"fibers" or "coroutines"的基本建筑物。使用光纤,您可以“暂停”等待异步调用返回的函数,从而有效地避免在“现场”声明回调函数并创建“闭包”。告别回电地狱吧。
...他说了一些关于调用堆栈和异常的事情
“闭包”的问题在于即使它们“神奇地”保持回调的局部变量的状态,“闭包”也不能保持调用堆栈。
在回调时,通常,调用函数很久以前就已经返回了,因此调用函数上的任何“catch”块都无法捕获异步函数本身或回调中的异常。这是一个很大的问题。因此,您无法将回调+闭包与异常捕获结合起来。
...并会更改节点格局
如果你使用生成器构建一个帮助库,如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)
生成器是两件事的组合 - Iterator
和Observer
。
迭代器在被调用时返回一个可迭代的东西,这是你可以迭代的东西。从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}将值发送回生成器}}
生成器广泛用于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
。生成器的waiting
和pause
属性允许我们执行这个惊人的技巧。
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
属性的对象。
async await
的新语法可让您等待async
事件在后台使用。了解生成器的工作方式将使您对这种构造的工作方式有更好的了解。答案 4 :(得分:0)
要在节点中使用ES6生成器,您需要安装node&gt; = 0.11.2
或iojs。
在节点中,您需要引用和声标志:
$ node --harmony app.js
或者您可以明确地引用生成器标志
$ node --harmony_generators app.js
如果你安装了iojs,你可以省略和声标志。
$ iojs app.js
有关如何使用生成器的高级概述,checkout this post。