I was at a node.js meetup today, and someone I met there said that node.js has es6 generators. He said that this is a huge improvement over callback style programming, and would change the node landscape. Iirc, he said something about call stack and exceptions.
我在一个节点上。今天js meetup,我在那里遇到的一个人说这个节点。js es6发电机。他说,这是对回调风格编程的巨大改进,并将改变节点的环境。Iirc,他提到了调用堆栈和异常。
I looked them up, but haven't really found any resource that explains them in a beginner-friendly way. What's a high-level overview of generators, and how are the different (or better?) than callbacks?
我查阅了他们的资料,但并没有找到任何能以初学者友好的方式解释他们的资料。生成器的高级概述是什么?与回调有什么不同(或更好?)
PS: It'd be really helpful if you could give a snippet of code to highlight the difference in common scenarios (making an http request or a db call).
PS:如果您能提供一个代码片段来突出常见场景的差异(发出http请求或db调用),那将非常有帮助。
4 个解决方案
#1
13
Generators, fibers and coroutines
"Generators" (besides being "generators") are also the basic buildings blocks of "fibers" or "coroutines". With fibers, you can "pause" a function waiting for an async call to return, effectively avoiding to declare a callback function "on the spot" and creating a "closure". Say goodbye to callback hell.
“发电机”(除了“发电机”之外)也是“纤维”或“警戒线”的基本建筑单元。使用纤程,您可以“暂停”一个等待异步调用返回的函数,有效地避免“当场”声明回调函数并创建“闭包”。跟回电话说再见。
Closures and try-catch
...he said something about call stack and exceptions
…他谈到了调用堆栈和异常
The problem with "closures" is that even if they "magically" keep the state of the local variables for the callback, a "closure" can not keep the call stack.
“闭包”的问题是,即使它们“神奇地”保留回调的本地变量的状态,“闭包”也不能保留调用堆栈。
At the moment of callback, normally, the calling function has returned a long time ago, so any "catch" block on the calling function cannot catch exceptions in the async function itself or the callback. This presents a big problem. Because of this, you can not combine callbacks+closures with exception catching.
在回调的时刻,通常,调用函数已经返回很久了,因此调用函数上的任何“catch”块都不能捕获异步函数本身或回调函数中的异常。这是一个大问题。因此,您不能将回调+闭包与异常捕获结合在一起。
Wait.for
...and would change the node landscape
…会改变节点的景观
If you use generators to build a helper lib like Wait.for-ES6 (I'm the author), you can completely avoid the callback and the closure, and now "catch blocks" work as expected, and the code is straightforward.
如果您使用生成器来构建一个助手库,比如Wait。对于es6(我是作者),您可以完全避免回调和闭包,现在可以按照预期“捕获块”工作,代码很简单。
It'd be really helpful if you could give a snippet of code to highlight the difference in common scenarios (making an http request or a db call).
如果您能提供一段代码来突出常见场景中的差异(发出http请求或db调用),这将非常有帮助。
Check Wait.for-ES6 examples, to see the same code with callbacks and with fibers based on generators.
检查等。对于es6示例,可以看到使用回调和基于生成器的纤程的相同代码。
#2
8
Generators is one of many features in upcoming ES6. So in the future it will be possible to use them in browsers (right now you can play with them in FF).
生成器是即将到来的ES6中的许多特性之一。因此,将来在浏览器中使用它们是可能的(现在您可以在FF中使用它们)。
Generators are constructors for iterators. Sounds like gibberish, so in easier terms they allow to create objects that later will be possible to iterate with something like for loops using .next()
method.
生成器是迭代器的构造函数。听起来像gibberish,因此在更简单的条件下,它们允许创建对象,以便以后可以使用.next()方法迭代一些类似于for循环的对象。
Generators are defined in a similar way to functions. Except they have *
and yield
in them. * is to tell that this is generator, yield is similar to return.
生成器以类似于函数的方式定义。除了他们有*和屈服。*是说这是生成器,收益率与收益率相似。
For example this is a generator:
例如,这是一个生成器:
function *seq(){
var n = 0;
while (true) yield n++;
}
Then you can use this generator with var s = seq()
. But in contrast to a function it will not execute everything and give you a result, it will just instantiate the generator. Only when you will run s.next()
the generator will be executed. Here yield is similar to return, but when the yield will run, it will pause the the generator and continues to work on the next expression after next. But when the next s.next()
will be called, the generator will resume its execution. In this case it will continue doing while loop forever.
然后可以使用var s = seq()这个生成器。但与函数不同的是,它不会执行所有操作并给出结果,它只会实例化生成器。只有在运行.next()时才会执行生成器。这里的yield类似于return,但是当yield运行时,它将暂停生成器,并在next之后继续处理下一个表达式。但是当调用下一个.next()时,生成器将继续执行。在这种情况下,它将继续执行while循环。
So you can iterate this with
你可以迭代它
for (var i = 0; i < 5; i++){
console.log( s.next().value )
}
or with a specific of construct for generators:
或有特定的发电机结构:
for (var n of seq()){
if (n >=5) break;
console.log(n);
}
These are basics about generators (you can look at yield*
, next(with_params)
, throw()
and other additional constructs). Note that it is about generators in ES6 (so you can do all this in node and in browser).
这些是关于生成器的基本知识(您可以查看yield*、next(with_params)、throw()和其他附加结构)。注意,它是关于ES6中的生成器(所以您可以在node和浏览器中完成所有这些工作)。
But how this infinite number sequence has anything to do with callback?
但是这个无限数列和回调有什么关系呢?
Important thing here is that yield pauses the generator. So imagine you have a very strange system which work this way:
这里重要的是,yield暂停了生成器。想象一下你有一个非常奇怪的系统是这样运作的:
You have database with users and you need to find the name of a user with some ID, then you need to check in your file system the key for a this user's name and then you need to connect to some ftp with user's id and key and do something after connection. (Sounds ridiculous but I want to show nested callbacks).
数据库与用户,你需要找到一个用户的名称和ID,那么您需要检查在您的文件系统关键用户的名字,然后你需要连接一些ftp用户ID和关键,连接后做点什么。(听起来很荒谬,但我想显示嵌套回调)。
Previously you would write something like this:
之前你会这样写:
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);
})
})
});
Which is callback inside callback inside callback inside callback. Now you can write something like:
也就是回调内部的回调。现在你可以这样写:
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);
And then use it with s.next();
As you see there is no nested callbacks.
然后与。next()一起使用;如您所见,没有嵌套回调。
Because node heavily uses nested callbacks, this is the reason why the guy was telling that generators can change the landscape of node.
由于node大量使用嵌套回调,这就是为什么这个家伙说生成器可以改变node的前景。
#3
6
A generator is a combination of two things - an Iterator
and an Observer
.
生成器是两个东西的组合——迭代器和观察者。
Iterator
An iterator is something when invoked returns an iterable which is something you can iterate upon. From ES6 onwards, all collections (Array, Map, Set, WeakMap, WeakSet) conform to the Iterable contract.
迭代器是在调用时返回可迭代的东西,您可以对它进行迭代。从ES6开始,所有集合(数组、映射、集合、弱映射、弱集)都符合可迭代契约。
A generator(iterator) is a producer. In iteration the consumer
PULL
s the value from the producer.生成器(迭代器)是一个生成器。在迭代中,消费者从生产者那里获取价值。
Example:
例子:
function *gen() { yield 5; yield 6; }
let a = gen();
Whenever you call a.next()
, you're essentially pull
-ing value from the Iterator and pause
the execution at yield
. The next time you call a.next()
, the execution resumes from the previously paused state.
无论何时调用a.next(),实际上都是从迭代器中提取值,并在yield处暂停执行。下一次调用a.next()时,执行将从先前暂停的状态恢复。
Observer
A generator is also an observer using which you can send some values back into the generator. Explained better with examples.
生成器也是一个观察者,您可以使用它向生成器发送一些值。更好的例子来解释。
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);
}
Here you can see that yield 1
is used like an expression which evaluates to some value. The value it evaluates to is the value sent as an argument to the a.next
function call.
在这里,您可以看到,yield 1被用作计算某个值的表达式。它计算的值是作为参数发送给a的值。下一个函数调用。
So, for the first time i.value
will be the first value yielded (1
), and when continuing the iteration to the next state, we send a value back to the generator using a.next(100)
.
第一次。值将是第一个生成的值(1),当继续迭代到下一个状态时,我们使用a.next(100)将一个值返回到生成器。
Where can you use this in Node.JS?
Generators are widely used with spawn
(from taskJS or co) function, where the function takes in a generator and allows us to write asynchronous code in a synchronous fashion. This does NOT mean that async code is converted to sync code / executed synchronously. It means that we can write code that looks like sync
but internally it is still async
.
生成器被广泛用于spawn(来自taskJS或co)函数,该函数接收生成器,并允许我们以同步的方式编写异步代码。这并不意味着异步代码被转换为同步代码/同步执行。这意味着我们可以编写看起来像同步的代码,但在内部它仍然是异步的。
Sync is BLOCKING; Async is WAITING. Writing code that blocks is easy. When PULLing, value appears in the assignment position. When PUSHing, value appears in the argument position of the callback
同步阻塞;异步是等待。编写阻塞的代码很容易。当拉动时,值出现在赋值位置。按下时,值出现在回调的参数位置
When you use iterators, you PULL
the value from the producer. When you use callbacks, the producer PUSH
es the value to the argument position of the callback.
当您使用迭代器时,您将从生成器中提取值。当您使用回调时,生成器将值推到回调的参数位置。
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Here, you pull the value from a.next()
and in the second, v => {...}
is the callback and a value is PUSH
ed into the argument position v
of the callback function.
这里,从a.next()中提取值,在第二个中,v =>{…}是回调,一个值被推入回调函数的参数位置v。
Using this pull-push mechanism, we can write async programming like this,
使用这个拖拽机制,我们可以像这样编写异步编程,
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
});
So, looking at the above code, we are writing async code that looks like it's blocking
(the yield statements wait for 100ms and then continue execution), but it's actually waiting
. The pause
and resume
property of generator allows us to do this amazing trick.
因此,看看上面的代码,我们正在编写一个看起来像阻塞的异步代码(yield语句等待100ms,然后继续执行),但是它实际上在等待。生成器的暂停和恢复属性允许我们执行这个惊人的技巧。
How does it work ?
The spawn function uses yield promise
to PULL the promise state from the generator, waits till the promise is resolved, and PUSHes the resolved value back to the generator so it can consume it.
派生函数使用yield promise从生成器中提取承诺状态,等待承诺被解析,并将解析后的值推回生成器,以便它可以使用它。
Use it now
So, with generators and spawn function, you can clean up all your async code in NodeJS to look and feel like it's synchronous. This will make debugging easy. Also the code will look neat.
因此,使用生成器和衍生函数,您可以清理NodeJS中的所有异步代码,使其看起来和感觉上都是同步的。这将使调试变得容易。代码看起来也很整洁。
BTW, this is coming to JavaScript natively for ES2017 - as async...await
. But you can use them today in ES2015/ES6 and ES2016 using the spawn function defined in the libraries - taskjs, co, or bluebird
顺便说一句,这是JavaScript在2017 -异步等待。但是你现在可以在ES2015/ES6和ES2016中使用在库中定义的衍生函数—taskjs、co或bluebird
#4
0
To use the ES6 generators in node, you will need to either install node >= 0.11.2
or iojs.
要在node中使用ES6生成器,您需要安装node >= 0.11.2或iojs。
In node, you will need to reference the harmony flag:
在“节点”中,您需要参考“和谐”标志:
$ node --harmony app.js
or you can explicitly just reference the generators flag
或者您可以显式地引用生成器标志
$ node --harmony_generators app.js
If you've installed iojs, you can omit the harmony flag.
如果您已经安装了iojs,您可以省略harmony标志。
$ iojs app.js
For a high level overview on how to use generators, checkout this post.
关于如何使用生成器的高级概述,请查看本文。
#1
13
Generators, fibers and coroutines
"Generators" (besides being "generators") are also the basic buildings blocks of "fibers" or "coroutines". With fibers, you can "pause" a function waiting for an async call to return, effectively avoiding to declare a callback function "on the spot" and creating a "closure". Say goodbye to callback hell.
“发电机”(除了“发电机”之外)也是“纤维”或“警戒线”的基本建筑单元。使用纤程,您可以“暂停”一个等待异步调用返回的函数,有效地避免“当场”声明回调函数并创建“闭包”。跟回电话说再见。
Closures and try-catch
...he said something about call stack and exceptions
…他谈到了调用堆栈和异常
The problem with "closures" is that even if they "magically" keep the state of the local variables for the callback, a "closure" can not keep the call stack.
“闭包”的问题是,即使它们“神奇地”保留回调的本地变量的状态,“闭包”也不能保留调用堆栈。
At the moment of callback, normally, the calling function has returned a long time ago, so any "catch" block on the calling function cannot catch exceptions in the async function itself or the callback. This presents a big problem. Because of this, you can not combine callbacks+closures with exception catching.
在回调的时刻,通常,调用函数已经返回很久了,因此调用函数上的任何“catch”块都不能捕获异步函数本身或回调函数中的异常。这是一个大问题。因此,您不能将回调+闭包与异常捕获结合在一起。
Wait.for
...and would change the node landscape
…会改变节点的景观
If you use generators to build a helper lib like Wait.for-ES6 (I'm the author), you can completely avoid the callback and the closure, and now "catch blocks" work as expected, and the code is straightforward.
如果您使用生成器来构建一个助手库,比如Wait。对于es6(我是作者),您可以完全避免回调和闭包,现在可以按照预期“捕获块”工作,代码很简单。
It'd be really helpful if you could give a snippet of code to highlight the difference in common scenarios (making an http request or a db call).
如果您能提供一段代码来突出常见场景中的差异(发出http请求或db调用),这将非常有帮助。
Check Wait.for-ES6 examples, to see the same code with callbacks and with fibers based on generators.
检查等。对于es6示例,可以看到使用回调和基于生成器的纤程的相同代码。
#2
8
Generators is one of many features in upcoming ES6. So in the future it will be possible to use them in browsers (right now you can play with them in FF).
生成器是即将到来的ES6中的许多特性之一。因此,将来在浏览器中使用它们是可能的(现在您可以在FF中使用它们)。
Generators are constructors for iterators. Sounds like gibberish, so in easier terms they allow to create objects that later will be possible to iterate with something like for loops using .next()
method.
生成器是迭代器的构造函数。听起来像gibberish,因此在更简单的条件下,它们允许创建对象,以便以后可以使用.next()方法迭代一些类似于for循环的对象。
Generators are defined in a similar way to functions. Except they have *
and yield
in them. * is to tell that this is generator, yield is similar to return.
生成器以类似于函数的方式定义。除了他们有*和屈服。*是说这是生成器,收益率与收益率相似。
For example this is a generator:
例如,这是一个生成器:
function *seq(){
var n = 0;
while (true) yield n++;
}
Then you can use this generator with var s = seq()
. But in contrast to a function it will not execute everything and give you a result, it will just instantiate the generator. Only when you will run s.next()
the generator will be executed. Here yield is similar to return, but when the yield will run, it will pause the the generator and continues to work on the next expression after next. But when the next s.next()
will be called, the generator will resume its execution. In this case it will continue doing while loop forever.
然后可以使用var s = seq()这个生成器。但与函数不同的是,它不会执行所有操作并给出结果,它只会实例化生成器。只有在运行.next()时才会执行生成器。这里的yield类似于return,但是当yield运行时,它将暂停生成器,并在next之后继续处理下一个表达式。但是当调用下一个.next()时,生成器将继续执行。在这种情况下,它将继续执行while循环。
So you can iterate this with
你可以迭代它
for (var i = 0; i < 5; i++){
console.log( s.next().value )
}
or with a specific of construct for generators:
或有特定的发电机结构:
for (var n of seq()){
if (n >=5) break;
console.log(n);
}
These are basics about generators (you can look at yield*
, next(with_params)
, throw()
and other additional constructs). Note that it is about generators in ES6 (so you can do all this in node and in browser).
这些是关于生成器的基本知识(您可以查看yield*、next(with_params)、throw()和其他附加结构)。注意,它是关于ES6中的生成器(所以您可以在node和浏览器中完成所有这些工作)。
But how this infinite number sequence has anything to do with callback?
但是这个无限数列和回调有什么关系呢?
Important thing here is that yield pauses the generator. So imagine you have a very strange system which work this way:
这里重要的是,yield暂停了生成器。想象一下你有一个非常奇怪的系统是这样运作的:
You have database with users and you need to find the name of a user with some ID, then you need to check in your file system the key for a this user's name and then you need to connect to some ftp with user's id and key and do something after connection. (Sounds ridiculous but I want to show nested callbacks).
数据库与用户,你需要找到一个用户的名称和ID,那么您需要检查在您的文件系统关键用户的名字,然后你需要连接一些ftp用户ID和关键,连接后做点什么。(听起来很荒谬,但我想显示嵌套回调)。
Previously you would write something like this:
之前你会这样写:
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);
})
})
});
Which is callback inside callback inside callback inside callback. Now you can write something like:
也就是回调内部的回调。现在你可以这样写:
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);
And then use it with s.next();
As you see there is no nested callbacks.
然后与。next()一起使用;如您所见,没有嵌套回调。
Because node heavily uses nested callbacks, this is the reason why the guy was telling that generators can change the landscape of node.
由于node大量使用嵌套回调,这就是为什么这个家伙说生成器可以改变node的前景。
#3
6
A generator is a combination of two things - an Iterator
and an Observer
.
生成器是两个东西的组合——迭代器和观察者。
Iterator
An iterator is something when invoked returns an iterable which is something you can iterate upon. From ES6 onwards, all collections (Array, Map, Set, WeakMap, WeakSet) conform to the Iterable contract.
迭代器是在调用时返回可迭代的东西,您可以对它进行迭代。从ES6开始,所有集合(数组、映射、集合、弱映射、弱集)都符合可迭代契约。
A generator(iterator) is a producer. In iteration the consumer
PULL
s the value from the producer.生成器(迭代器)是一个生成器。在迭代中,消费者从生产者那里获取价值。
Example:
例子:
function *gen() { yield 5; yield 6; }
let a = gen();
Whenever you call a.next()
, you're essentially pull
-ing value from the Iterator and pause
the execution at yield
. The next time you call a.next()
, the execution resumes from the previously paused state.
无论何时调用a.next(),实际上都是从迭代器中提取值,并在yield处暂停执行。下一次调用a.next()时,执行将从先前暂停的状态恢复。
Observer
A generator is also an observer using which you can send some values back into the generator. Explained better with examples.
生成器也是一个观察者,您可以使用它向生成器发送一些值。更好的例子来解释。
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);
}
Here you can see that yield 1
is used like an expression which evaluates to some value. The value it evaluates to is the value sent as an argument to the a.next
function call.
在这里,您可以看到,yield 1被用作计算某个值的表达式。它计算的值是作为参数发送给a的值。下一个函数调用。
So, for the first time i.value
will be the first value yielded (1
), and when continuing the iteration to the next state, we send a value back to the generator using a.next(100)
.
第一次。值将是第一个生成的值(1),当继续迭代到下一个状态时,我们使用a.next(100)将一个值返回到生成器。
Where can you use this in Node.JS?
Generators are widely used with spawn
(from taskJS or co) function, where the function takes in a generator and allows us to write asynchronous code in a synchronous fashion. This does NOT mean that async code is converted to sync code / executed synchronously. It means that we can write code that looks like sync
but internally it is still async
.
生成器被广泛用于spawn(来自taskJS或co)函数,该函数接收生成器,并允许我们以同步的方式编写异步代码。这并不意味着异步代码被转换为同步代码/同步执行。这意味着我们可以编写看起来像同步的代码,但在内部它仍然是异步的。
Sync is BLOCKING; Async is WAITING. Writing code that blocks is easy. When PULLing, value appears in the assignment position. When PUSHing, value appears in the argument position of the callback
同步阻塞;异步是等待。编写阻塞的代码很容易。当拉动时,值出现在赋值位置。按下时,值出现在回调的参数位置
When you use iterators, you PULL
the value from the producer. When you use callbacks, the producer PUSH
es the value to the argument position of the callback.
当您使用迭代器时,您将从生成器中提取值。当您使用回调时,生成器将值推到回调的参数位置。
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
Here, you pull the value from a.next()
and in the second, v => {...}
is the callback and a value is PUSH
ed into the argument position v
of the callback function.
这里,从a.next()中提取值,在第二个中,v =>{…}是回调,一个值被推入回调函数的参数位置v。
Using this pull-push mechanism, we can write async programming like this,
使用这个拖拽机制,我们可以像这样编写异步编程,
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
});
So, looking at the above code, we are writing async code that looks like it's blocking
(the yield statements wait for 100ms and then continue execution), but it's actually waiting
. The pause
and resume
property of generator allows us to do this amazing trick.
因此,看看上面的代码,我们正在编写一个看起来像阻塞的异步代码(yield语句等待100ms,然后继续执行),但是它实际上在等待。生成器的暂停和恢复属性允许我们执行这个惊人的技巧。
How does it work ?
The spawn function uses yield promise
to PULL the promise state from the generator, waits till the promise is resolved, and PUSHes the resolved value back to the generator so it can consume it.
派生函数使用yield promise从生成器中提取承诺状态,等待承诺被解析,并将解析后的值推回生成器,以便它可以使用它。
Use it now
So, with generators and spawn function, you can clean up all your async code in NodeJS to look and feel like it's synchronous. This will make debugging easy. Also the code will look neat.
因此,使用生成器和衍生函数,您可以清理NodeJS中的所有异步代码,使其看起来和感觉上都是同步的。这将使调试变得容易。代码看起来也很整洁。
BTW, this is coming to JavaScript natively for ES2017 - as async...await
. But you can use them today in ES2015/ES6 and ES2016 using the spawn function defined in the libraries - taskjs, co, or bluebird
顺便说一句,这是JavaScript在2017 -异步等待。但是你现在可以在ES2015/ES6和ES2016中使用在库中定义的衍生函数—taskjs、co或bluebird
#4
0
To use the ES6 generators in node, you will need to either install node >= 0.11.2
or iojs.
要在node中使用ES6生成器,您需要安装node >= 0.11.2或iojs。
In node, you will need to reference the harmony flag:
在“节点”中,您需要参考“和谐”标志:
$ node --harmony app.js
or you can explicitly just reference the generators flag
或者您可以显式地引用生成器标志
$ node --harmony_generators app.js
If you've installed iojs, you can omit the harmony flag.
如果您已经安装了iojs,您可以省略harmony标志。
$ iojs app.js
For a high level overview on how to use generators, checkout this post.
关于如何使用生成器的高级概述,请查看本文。