ES6生成器的基础

One of the most exciting new features coming in JavaScript ES6 is a new breed of function, called a generator. The name is a little strange, but the behavior may seem a lot stranger at first glance. This article aims to explain the basics of how they work, and build you up to understanding why they are so powerful for the future of JS.

JavaScript ES6中最激动人心的新功能之一是一种称为Generator的新功能。 这个名字有点奇怪,但是乍一看,这种行为似乎很陌生 。 本文旨在解释它们如何工作的基础知识,并帮助您理解为什么它们对于JS的未来如此强大。

完成运行 (Run-To-Completion)

The first thing to observe as we talk about generators is how they differ from normal functions with respect to the "run to completion" expectation.

当我们谈论生成器时,首先要观察的是它们在“运行到完成”期望方面与正常功能有何不同。

Whether you realized it or not, you've always been able to assume something fairly fundamental about your functions: once the function starts running, it will always run to completion before any other JS code can run.

不管您是否意识到,您始终可以假设一些基本的功能:一旦函数开始运行,它将始终运行到完成,然后再运行任何其他JS代码。

Example:

例:

setTimeout(function(){
    console.log("Hello World");
},1);

function foo() {
    // NOTE: don't ever do crazy long-running loops like this
    for (var i=0; i<=1E10; i++) {
        console.log(i);
    }
}

foo();
// 0..1E10
// "Hello World"


Here, the for loop will take a fairly long time to complete, well more than one millisecond, but our timer callback with the console.log(..) statement cannot interrupt the foo() function while it's running, so it gets stuck at the back of the line (on the event-loop) and it patiently waits its turn.

在这里, for循环将花费相当长的时间,远远超过一毫秒,但是带有console.log(..)语句的计时器回调无法在运行时中断foo()函数,因此它停留在行的结尾(在事件循环上),它耐心等待其转弯。

What if foo() could be interrupted, though? Wouldn't that cause havoc in our programs?

但是,如果foo()可以被中断怎么办? 那不会在我们的程序中造成破坏吗?

That's exactly the nightmares challenges of multi-threaded programming, but we are quite fortunate in JavaScript land to not have to worry about such things, because JS is always single-threaded (only one command/function executing at any given time).

那就是 噩梦 多线程编程面临许多挑战,但是我们非常幸运的是,不必担心这些事情,因为JS始终是单线程的(在任何给定时间仅执行一个命令/函数)。

Note: Web Workers are a mechanism where you can spin up a whole separate thread for a part of a JS program to run in, totally in parallel to your main JS program thread. The reason this doesn't introduce multi-threaded complications into our programs is that the two threads can only communicate with each other through normal async events, which always abide by the event-loop one-at-a-time behavior required by run-to-completion.

注意: Web Workers是一种机制,您可以在其中旋转整个完整的线程,以使JS程序的一部分可以在其中运行,而该进程完全与您的主要JS程序线程并行。 这样做不会在我们的程序中引入多线程复杂性的原因是,这两个线程只能通过正常的异步事件相互通信,而异步事件始终遵守运行要求的事件循环一次行为。完成。

运行..停止..运行 (Run..Stop..Run)

With ES6 generators, we have a different kind of function, which may be paused in the middle, one or many times, and resumed later, allowing other code to run during these paused periods.

随着ES6发电机,我们有不同类型的功能,可以在中间,一个或多次被暂停了, 后来恢复了,允许其他代码在这些暂停期间运行。

If you've ever read anything about concurrency or threaded programming, you may have seen the term "cooperative", which basically indicates that a process (in our case, a function) itself chooses when it will allow an interruption, so that it can cooperate with other code. This concept is contrasted with "preemptive", which suggests that a process/function could be interrupted against its will.

如果您曾经阅读过有关并发或线程编程的文章,那么您可能已经看到了“合作”一词,它基本上表示一个进程(在我们的例子中是一个函数)本身会选择允许中断的时间,以便与其他代码合作 。 该概念与“抢先式”形成对比,“抢先式”表示过程/功能可能会违反其意愿而中断。

ES6 generator functions are "cooperative" in their concurrency behavior. Inside the generator function body, you use the new yield keyword to pause the function from inside itself. Nothing can pause a generator from the outside; it pauses itself when it comes across a yield.

ES6生成器功能的并发行为是“合作的”。 在生成器函数体内,您可以使用new yield关键字从内部暂停函数。 没有什么可以使发电机停在外面。 当遇到yield时,它会自行停顿。

However, once a generator has yield-paused itself, it cannot resume on its own. An external control must be used to restart the generator. We'll explain how that happens in just a moment.

但是,一旦生成器暂停了yield ,它就无法自行恢复。 必须使用外部控件重新启动发电机。 我们将在短时间内说明如何发生。

So, basically, a generator function can stop and be restarted, as many times as you choose. In fact, you can specify a generator function with an infinite loop (like the infamous while (true) { .. }) that essentially never finishes. While that's usually madness or a mistake in a normal JS program, with generator functions it's perfectly sane and sometimes exactly what you want to do!

因此,基本上,生成器功能可以停止并重新启动,次数不限。 实际上,您可以指定一个带有无限循环的生成器函数(例如,臭名昭著的while (true) { .. } ),该循环基本上不会结束。 虽然这通常是一个普通的JS程序中的疯狂或错误,但是使用生成器函数,它是完全理智的,有时甚至是您想要执行的操作!

Even more importantly, this stopping and starting is not just a control on the execution of the generator function, but it also enables 2-way message passing into and out of the generator, as it progresses. With normal functions, you get parameters at the beginning and a return value at the end. With generator functions, you send messages out with each yield, and you send messages back in with each restart.

更重要的是,这种停止和启动不仅对发电机功能的执行的控制,但它也使2路的消息传递进出发电机的,因为它的进展。 使用普通函数,您可以在开头获取参数,并在结尾获取return值。 使用生成器功能时,您将以每个yield发送消息,并在每次重新启动时将消息发送回。

语法请! (Syntax Please!)

Let's dig into the syntax of these new and exciting generator functions.

让我们深入研究这些令人兴奋的新生成器函数的语法。

First, the new declaration syntax:

首先,新的声明语法:

function *foo() {
    // ..
}


Notice the * there? That's new and a bit strange looking. To those from some other languages, it may look an awful lot like a function return-value pointer. But don't get confused! This is just a way to signal the special generator function type.

注意那里的*吗? 这是新的,看起来有些奇怪。 对于其他语言的人来说,它看起来很像函数的返回值指针。 但是请不要感到困惑! 这只是发出特殊生成器功能类型信号的一种方式。

You've probably seen other articles/documentation which use function* foo(){ } instead of function *foo(){ } (difference in placement of the *). Both are valid, but I've recently decided that I think function *foo() { } is more accurate, so that's what I'm using here.

您可能已经看过其他文章/文档,它们使用function* foo(){ }而不是function *foo(){ } ( *位置不同)。 两者都是有效的,但是我最近决定,我认为function *foo() { }更准确,所以这就是我在这里使用的。

Now, let's talk about the contents of our generator functions. Generator functions are just normal JS functions in most respects. There's very little new syntax to learn inside the generator function.

现在,让我们讨论生成器函数的内容。 在大多数方面,生成器函数只是普通的JS函数。 生成器函数内部几乎没有新语法要学习。

The main new toy we have to play with, as mentioned above, is the yield keyword. yield ___ is called a "yield expression" (and not a statement) because when we restart the generator, we will send a value back in, and whatever we send in will be the computed result of that yield ___ expression.

如上所述,我们必须使用的主要新玩具是yield关键字。 yield ___被称为“ yield表达式”(而不是语句),因为当我们重新启动生成器时,我们将返回一个值,而我们发送的任何内容都是该yield ___表达式的计算结果。

Example:

例:

function *foo() {
    var x = 1 + (yield "foo");
    console.log(x);
}


The yield "foo" expression will send the "foo" string value out when pausing the generator function at that point, and whenever (if ever) the generator is restarted, whatever value is sent in will be the result of that expression, which will then get added to 1 and assigned to the x variable.

当暂停生成器函数时, yield "foo"表达式将发送"foo"字符串值,并且每当(如果有)重启生成器时,无论传入什么值都将是该表达式的结果,它将然后加到1并分配给x变量。

See the 2-way communication? You send the value "foo" out, pause yourself, and at some point later (could be immediately, could be a long time from now!), the generator will be restarted and will give you a value back. It's almost as if the yield keyword is sort of making a request for a value.

看到两路通讯? 您发送值"foo"了,暂停自己,在某些点以后 (可能马上,可能是从现在开始很长一段时间!),发电机将重新启动,并会给你一个值返回。 几乎就像yield关键字在要求一个值一样。

In any expression location, you can just use yield by itself in the expression/statement, and there's an assumed undefined value yielded out. So:

在任何表达式位置,您都可以在表达式/语句中单独使用yield ,并且会假定有一个undefinedyield 。 所以:

// note: `foo(..)` here is NOT a generator!!
function foo(x) {
    console.log("x: " + x);
}

function *bar() {
    yield; // just pause
    foo( yield ); // pause waiting for a parameter to pass into `foo(..)`
}


生成器迭代器 (Generator Iterator)

"Generator Iterator". Quite a mouthful, huh?

“生成器迭代器”。 满口,是吗?

Iterators are a special kind of behavior, a design pattern actually, where we step through an ordered set of values one at a time by calling next(). Imagine for example using an iterator on an array that has five values in it: [1,2,3,4,5]. The first next() call would return 1, the second next() call would return 2, and so on. After all values had been returned, next() would return null or false or otherwise signal to you that you've iterated over all the values in the data container.

迭代器是一种特殊的行为,实际上是一种设计模式,在这种情况下,我们通过调用next()一次遍历一组有序的值。 例如,想像一下在其中具有五个值的数组上使用迭代器: [1,2,3,4,5] 。 第一个next()调用将返回1 ,第二个next()调用将返回2 ,依此类推。 在返回所有值之后, next()将返回nullfalse ,否则将向您发出信号,通知您已遍历数据容器中的所有值。

The way we control generator functions from the outside is to construct and interact with a generator iterator. That sounds a lot more complicated than it really is. Consider this silly example:

我们从外部控制生成器功能的方法是构造生成器迭代器并与之交互。 听起来比实际要复杂得多。 考虑这个愚蠢的例子:

function *foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}


To step through the values of that *foo() generator function, we need an iterator to be constructed. How do we do that? Easy!

要逐步了解该*foo()生成器函数的值,我们需要构造一个迭代器。 我们该怎么做? 简单!

var it = foo();


Oh! So, calling the generator function in the normal way doesn't actually execute any of its contents.

哦! 因此,以常规方式调用generator函数实际上并不会执行其任何内容。

That's a little strange to wrap your head around. You also may be tempted to wonder, why isn't it var it = new foo(). Shrugs. The whys behind the syntax are complicated and beyond our scope of discussion here.

缠住你的头有点奇怪。 您可能还会想知道,为什么它不是var it = new foo() 。 耸耸肩。 语法背后的原因很复杂,超出了我们的讨论范围。

So now, to start iterating on our generator function, we just do:

因此,现在开始迭代生成器函数,我们只需执行以下操作:

var message = it.next();


That will give us back our 1 from the yield 1 statment, but that's not the only thing we get back.

这将赋予我们回1yield 1 statment,但是这不是我们回来的唯一的事。

console.log(message); // { value:1, done:false }


We actually get back an object from each next() call, which has a value property for the yielded-out value, and done is a boolean that indicates if the generator function has fully completed or not.

实际上,我们实际上是从每个next()调用中获取一个对象,该对象具有yield ed-out值的value属性,并且done是一个布尔值,指示生成器函数是否已完全完成。

Let's keep going with our iteration:

让我们继续进行迭代:

console.log( it.next() ); // { value:2, done:false }
console.log( it.next() ); // { value:3, done:false }
console.log( it.next() ); // { value:4, done:false }
console.log( it.next() ); // { value:5, done:false }


Interesting to note, done is still false when we get the value of 5 out. That's because technically, the generator function is not complete. We still have to call a final next() call, and if we send in a value, it has to be set as the result of that yield 5 expression. Only then is the generator function complete.

有趣的是,当我们得到5的值时, done仍然是false 。 这是因为从技术上讲 ,生成器功能并不完整。 我们仍然必须调用最终的next()调用,如果我们发送一个值,则必须将其设置为yield 5表达式的结果。 只有这样 ,生成器功能才能完成。

So, now:

因此,现在:

console.log( it.next() ); // { value:undefined, done:true }


So, the final result of our generator function was that we completed the function, but there was no result given (since we'd already exhausted all the yield ___ statements).

因此,生成器函数的最终结果是我们完成了该函数,但是没有给出任何结果(因为我们已经用尽了所有yield ___语句)。

You may wonder at this point, can I use return from a generator function, and if I do, does that value get sent out in the value property?

您可能会怀疑,此时,我可以使用生成器函数的return吗,如果可以,该值是否会在value属性中发送出去?

Yes...

是的

function *foo() {
    yield 1;
    return 2;
}

var it = foo();

console.log( it.next() ); // { value:1, done:false }
console.log( it.next() ); // { value:2, done:true }


... and no.

... 也没有

It may not be a good idea to rely on the return value from generators, because when iterating generator functions with for..of loops (see below), the final returned value would be thrown away.

依靠生成器的return值可能不是一个好主意,因为当使用for..of循环迭代生成器函数时(请参见下文),最终的return ed值将被丢弃。

For completeness sake, let's also take a look at sending messages both into and out of a generator function as we iterate it:

为了完整起见,让我们在迭代过程中还看一下向生成器函数发送消息和从生成器函数发送消息的过程:

function *foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var it = foo( 5 );

// note: not sending anything into `next()` here
console.log( it.next() );       // { value:6, done:false }
console.log( it.next( 12 ) );   // { value:8, done:false }
console.log( it.next( 13 ) );   // { value:42, done:true }


You can see that we can still pass in parameters (x in our example) with the initial foo( 5 ) iterator-instantiation call, just like with normal functions, making x be value 5.

您可以看到,仍然可以通过初始foo( 5 )迭代器实例化调用传入参数(在本例中为x ),就像使用普通函数一样,使x成为值5

The first next(..) call, we don't send in anything. Why? Because there's no yield expression to receive what we pass in.

第一个next(..)调用,我们什么也不发送。 为什么? 因为没有yield表达式可以接收我们传递的信息。

But if we did pass in a value to that first next(..) call, nothing bad would happen. It would just be a tossed-away value. ES6 says for generator functions to ignore the unused value in this case. (Note: At the time of writing, nightlies of both Chrome and FF are fine, but other browsers may not yet be fully compliant and may incorrectly throw an error in this case).

但是,如果我们确实将值传递给第next(..)调用,则不会发生任何不良情况。 这只是一个被抛弃的价值。 ES6表示,在这种情况下,生成器函数将忽略未使用的值。 ( 注意:在撰写本文时,Chrome和FF都可以使用,但是其他浏览器可能尚未完全兼容,在这种情况下可能会错误地引发错误)。

The yield (x + 1) is what sends out value 6. The second next(12) call sends 12 to that waiting yield (x + 1) expression, so y is set to 12 * 2, value 24. Then the subsequent yield (y / 3) (yield (24 / 3)) is what sends out the value 8. The third next(13) call sends 13 to that waiting yield (y / 3) expression, making z set to 13.

yield (x + 1)是输出值6 。 第二个next(12)调用将12发送到该等待的yield (x + 1)表达式,因此y设置为12 * 2 ,值24 。 然后,随后的yield (y / 3) ( yield (24 / 3) )发出值8 。 第三个next(13)调用将13发送到该等待的yield (y / 3)表达式,将z设置为13

Finally, return (x + y + z) is return (5 + 24 + 13), or 42 being returned out as the last value.

最后, return (x + y + z)return (5 + 24 + 13) ,或者42作为最后一个value被返回。

Re-read that a few times. It's weird for most, the first several times they see it.

重读几次。 对于大多数人来说,这是很奇怪的,这是他们最初几次看到的。

for..of (for..of)

ES6 also embraces this iterator pattern at the syntactic level, by providing direct support for running iterators to completion: the for..of loop.

ES6还通过为运行迭代器完成提供直接支持: for..of循环,从而在语法级别上包含此迭代器模式。

Example:

例:

function *foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6;
}

for (var v of foo()) {
    console.log( v );
}
// 1 2 3 4 5

console.log( v ); // still `5`, not `6` :(


As you can see, the iterator created by foo() is automatically captured by the for..of loop, and it's automatically iterated for you, one iteration for each value, until a done:true comes out. As long as done is false, it automatically extracts the value property and assigns it to your iteration variable (v in our case). Once done is true, the loop iteration stops (and does nothing with any final value returned, if any).

如您所见,由foo()创建的迭代器会被for..of循环自动捕获,并且会自动为您进行迭代,每个值进行一次迭代,直到done:true 。 只要donefalse ,它将自动提取value属性并将其分配给您的迭代变量(在本例中为v )。 一旦donetrue ,循环迭代停止(和什么也不做任何最终value返回,如果有的话)。

As noted above, you can see that the for..of loop ignores and throws away the return 6 value. Also, since there's no exposed next() call, the for..of loop cannot be used in situations where you need to pass in values to the generator steps as we did above.

如上所述,您可以看到for..of循环将忽略并丢弃return 6return 6 。 另外,由于没有公开的next()调用,因此在需要将值传递给生成器步骤的情况下,不能使用for..of循环,就像我们之前所做的那样。

摘要 (Summary)

OK, so that's it for the basics of generators. Don't worry if it's a little mind-bending still. All of us have felt that way at first!

好的,这就是生成器的基础知识。 如果这仍然有些令人不安,请不要担心。 我们所有人起初都有这种感觉!

It's natural to wonder what this new exotic toy is going to do practically for your code. There's a lot more to them, though. We've just scratched the surface. So we have to dive deeper before we can discover just how powerful they can/will be.

很自然地想知道这个新的异国情调的玩具实际上将为您的代码做什么。 但是,他们还有很多 。 我们只是擦了一下表面。 因此,我们必须深入研究,才能发现它们的功能/将有多强大。

After you've played around with the above code snippets (try Chrome nightly/canary or FF nightly, or node 0.11+ with the --harmony flag), the following questions may arise:

你与上面的代码片断发挥各地后(试用Chrome夜间/金丝雀或FF夜间或节点0.11+与--harmony标志),下面的问题可能会出现:

  1. How does error handling work?

    错误处理如何工作?
  2. Can one generator call another generator?

    一个发电机可以呼叫另一台发电机吗?
  3. How does async coding work with generators?

    异步编码如何与生成器一起使用?

Those questions, and more, will be covered in subsequent articles here, so stay tuned!

这些问题以及更多问题将在此处的后续文章中介绍,请继续关注!

翻译自: https://davidwalsh.name/es6-generators

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值