The Basics Of ES6 Generators
ES6生成器的基础
Diving Deeper With ES6 Generators
使用ES6生成器更深入地学习
Going Async With ES6 Generators
与ES6生成器异步
Getting Concurrent With ES6 Generators
与ES6生成器并发
If you've read and digested part 1, part 2, and part 3 of this blog post series, you're probably feeling pretty confident with ES6 generators at this point. Hopefully you're inspired to really push the envelope and see what you can do with them.
如果您已经阅读并理解了该博客文章系列的第1部分 , 第2部分和第3部分 ,那么此时您可能会对ES6生成器充满信心。 希望您受到启发,能够真正突破极限,看看您能用它们做什么。
Our final topic to explore is kinda bleeding edge stuff, and may twist your brain a bit (still twisting mine, TBH). Take your time working through and thinking about these concepts and examples. Definitely read other writings on the topic.
我们要探讨的最后一个主题是有点流血的东西,可能会使您的大脑有些扭曲(仍然扭曲着我的TBH)。 花些时间研究和思考这些概念和示例。 一定要阅读有关该主题的其他著作。
The investment you make here will really pay off in the long run. I'm totally convinced that the future of sophisticated async capability in JS is going to rise from these ideas.
从长远来看,您在这里进行的投资将真正获得回报。 我完全相信,从这些想法中可以看出JS中先进的异步功能的未来。
First off, I am completely inspired in this topic almost entirely due to the fantastic work of David Nolen @swannodette. Seriously, read whatever he writes on the topic. Here's some links to get you started:
首先,几乎完全由于David Nolen @swannodette的出色工作而使我对这个主题完全感到启发。 认真阅读他在该主题上写的任何内容。 以下是一些可帮助您入门的链接:
"Communicating Sequential Processes"
“通信顺序过程”
"ES6 Generators Deliver Go Style Concurrency"
“ ES6生成器提供Go样式并发性”
"Extracting Processes"
“提取过程”
OK, now to my exploration of the topic. I don't come to JS from a formal background in Clojure, nor do I have any experience with Go or ClojureScript. I found myself quickly getting kinda lost in those readings, and I had to do a lot of experimentation and educated guessing to glean useful bits from it.
好,现在我来探讨这个话题。 我不是从Clojure的正式背景来学习JS的,也没有Go或ClojureScript的经验。 我发现自己很快就迷失了这些读物,我不得不做大量实验并进行有根据的猜测,以从中收集有用的信息。
In the process, I think I've arrived at something that's of the same spirit, and goes after the same goals, but comes at it from a much-less-formal way of thinking.
在此过程中,我认为我已经达成了具有相同精神,追求相同目标的目标,但是这是通过一种不太正式的思维方式实现的。
What I've tried to do is build up a simpler take on the Go-style CSP (and ClojureScript core.async) APIs, while preserving (I hope!) most of the underlying capabilities. It's entirely possible that those smarter than me on this topic will quickly see things I've missed in my explorations thus far. If so, I hope my explorations will evolve and progress, and I'll keep sharing such revelations with you readers!
我试图做的是在保留(我希望!)大多数基本功能的同时,对Go风格的CSP(和ClojureScript core.async)API进行更简单的构建。 那些比我精明的人,很可能会很快发现我到目前为止在探索中错过的事情。 如果是这样,我希望我的探索会不断发展和进步,我将继续与您的读者分享这些启示!
What is CSP all about? What does it mean to say "communicating"? "Sequential"? What are these "processes"?
CSP到底是什么? 说“交流”是什么意思? “顺序”? 这些“过程”是什么?
First and foremost, CSP comes from Tony Hoare's book "Communicating Sequential Processes". It's heavy CS theory stuff, but if you're interested in the academic side of things, that's the best place to start. I am by no means going to tackle the topic in a heady, esoteric, computer sciency way. I'm going to come at it quite informally.
首先,CSP来自Tony Hoare的书“ Communicationing Sequential Processes” 。 这是沉重的CS理论资料,但是如果您对事物的学术性感兴趣,那么这是最好的起点。 我绝不打算以笨拙,深奥的计算机科学方式解决这个问题。 我将非正式地讨论它。
So, let's start with "sequential". This is the part you should already be familiar with. It's another way of talking about single-threaded behavior and the sync-looking code that we get from ES6 generators.
因此,让我们从“顺序”开始。 这是您应该已经熟悉的部分。 这是谈论单线程行为和从ES6生成器获得的同步代码的另一种方式。
Remember how generators have syntax like this:
记住生成器如何具有如下语法:
function *main() { var x = yield 1; var y = yield x; var z = yield (y * 2); }Each of those statements is executed sequentially (in order), one at a time. The yield keyword annotates points in the code where a blocking pause (blocking only in the sense of the generator code itself, not the surrounding program!) may occur, but that doesn't change anything about the top-down handling of the code inside *main(). Easy enough, right?
这些语句中的每条语句都按顺序(顺序)执行,一次执行。 yield关键字注释代码中可能发生阻塞暂停(仅在生成器代码本身而不是周围程序的意义上阻塞!)的点,但这不会改变内部代码的自顶向下处理方式*main() 。 很容易,对吧?
Next, let's talk about "processes". What's that all about?
接下来,让我们讨论“过程”。 那是什么意思
Essentially, a generator sort of acts like a virtual "process". It's a self-contained piece of our program that could, if JavaScript allowed such things, run totally in parallel to the rest of the program.
本质上,生成器的行为类似于虚拟的“过程”。 这是我们程序的一个独立部分,如果JavaScript允许这样的事情,它可以与程序的其余部分完全并行运行。
Actually, that'd fudging things a little bit. If the generator accesses shared memory (that is, if it accessed "free variables" besides its own internal local variables), it's not quite so independent. But let's just assume for now we have a generator function that doesn't access outside variables (so FP theory would call it a "combinator"). So, it could in theory run in/as its own process.
实际上,这会使事情有些混乱。 如果生成器访问共享内存(也就是说,如果生成器访问其自己的内部局部变量之外的“自由变量”),则它并不是那么独立。 但是,现在让我们假设我们有一个生成器函数,该函数不访问外部变量(因此FP理论将其称为“组合器”)。 因此,它在理论上可以按自己的过程运行。
But we said "processes" -- plural -- because the important part here is having two or more going at once. In other words, two or more generators that are paired together, generally to cooperate to complete some bigger task.
因为这里的重要组成部分,具有两个或在一次去-但是,我们说,“过程” -复数。 换句话说,两个或更多的发电机配对在一起,通常可以合作完成一些更大的任务。
Why separate generators instead of just one? The most important reason: separation of capabilities/concerns. If you can look at task XYZ and break it down into constituent sub-tasks like X, Y, and Z, then implementing each in its own generator tends to lead to code that can be more easily reasoned about and maintained.
为什么要分离发电机而不是仅仅一个? 最重要的原因: 能力/关注点分离 。 如果您可以查看任务XYZ并将其分解为诸如X,Y和Z的子任务,那么在自己的生成器中实现每个任务往往会导致代码更容易推理和维护。
This is the same sort of reasoning you use when you take a function like function XYZ() and break it down into X(), Y(), and Z() functions, where X() calls Y(), and Y() calls Z(), etc. We break down functions into separate functions to get better separation of code, which makes code easier to maintain.
这与使用诸如function XYZ()函数之类的function XYZ()并将其分解为X() , Y()和Z()函数(其中X()调用Y()和Y()调用Z()等。我们将函数分解为单独的函数,以更好地分离代码,这使代码更易于维护。
We can do the same thing with multiple generators.
我们可以使用多个生成器来做同样的事情。
Finally, "communicating". What's that all about? It flows from the above -- cooperation -- that if the generators are going to work together, they need a communication channel (not just access to the shared surrounding lexical scope, but a real shared communication channel they all are given exclusive access to).
最后是“交流”。 那是什么意思 以上是合作的结果,即如果生成器要一起工作,则它们需要一个通信通道(不仅访问共享的周围词法范围,而且还拥有一个真正共享的通信通道,它们都被授予独占访问权限) 。
What goes over this communication channel? Whatever you need to send (numbers, strings, etc). In fact, you don't even need to actually send a message over the channel to communicate over the channel. "Communication" can be as simple as coordination -- like transferring control from one to another.
这个沟通渠道会发生什么? 无论您需要发送什么(数字,字符串等)。 实际上,您甚至不需要通过通道实际发送消息即可通过通道进行通信。 “通信”可以像协调一样简单-就像将控制权从一个转移到另一个。
Why transferring control? Primarily because JS is single-threaded and literally only one of them can be actively running at any given moment. The others then are in a running-paused state, which means they're in the middle of their tasks, but are just suspended, waiting to be resumed when necessary.
为什么要转移控制权? 主要是因为JS是单线程的,实际上在任何给定的时刻只有其中一个可以主动运行。 其他人则处于运行暂停状态,这意味着他们处于任务的中间,但只是被暂停,等待在必要时恢复。
It doesn't seem to be realistic that arbitrary independent "processes" could magically cooperate and communicate. The goal of loose coupling is admirable but impractical.
任意独立的“过程”可以神奇地协作和交流似乎并不现实。 松耦合的目标是令人钦佩的,但却不切实际。
Instead, it seems like any successful implementation of CSP is an intentional factorization of an existing, well-known set of logic for a problem domain, where each piece is designed specifically to work well with the other pieces.
取而代之的是,似乎CSP的任何成功实施都是对问题域现有的,众所周知的一组逻辑的有意分解,其中,每个部分都经过专门设计,可以与其他部分很好地协同工作。
Maybe I'm totally wrong on this, but I don't see any pragmatic way yet that any two random generator functions could somehow easily be glued together into a CSP pairing. They would both need to be designed to work with the other, agree on the communication protocol, etc.
也许我对此完全错了,但是我还没有看到任何务实的方式可以将任意两个随机生成器函数轻松地粘合在一起成为CSP配对。 他们都需要被设计成可以与其他人一起工作,就通信协议达成一致,等等。
There are several interesting explorations in CSP theory applied to JS.
CSP理论在JS中有一些有趣的探索。
The aforementioned David Nolen has several interesting projects, including Om, as well as core.async. The Koa library (for node.js) has a very interesting take, primarily through its use(..) method. Another library that's pretty faithful to the core.async/Go CSP API is js-csp.
前面提到的David Nolen有几个有趣的项目,包括Om以及core.async 。 Koa库(用于node.js)有一个非常有趣的方法,主要是通过use(..)方法实现的。 另一个非常忠于core.async / Go CSP API的库是js-csp 。
You should definitely check out those great projects to see various approaches and examples of how CSP in JS is being explored.
您绝对应该检查那些很棒的项目,以查看各种方法以及如何在JS中探索CSP的示例。
Since I've been trying intensely to explore applying the CSP pattern of concurrency to my own JS code, it was a natural fit for me to extend my async flow-control lib asynquence with CSP capability.
因为我一直强烈地探索应用并发的CSP模式,以我自己的JS代码,这是一个自然的适合我致以异步流量控制的lib asynquence与CSP能力。
I already had the runner(..) plugin utility which handles async running of generators (see "Part 3: Going Async With Generators"), so it occurred to me that it could be fairly easily extended to handle multiple generators at the same time in a CSP-like fashion.
我已经有了runner(..)插件实用程序,该实用程序可以处理生成器的异步运行(请参阅“第3部分:使用生成器进行异步” ),因此我想到可以相当轻松地将其扩展为同时处理多个生成器。 以类似CSP的方式 。
The first design question I tackled: how do you know which generator gets control next?
我解决的第一个设计问题:您如何知道接下来要控制哪个发电机?
It seemed overly cumbersome/clunky to have each one have some sort of ID that the others have to know about, so they can address their messages or control-transfer explicitly to another process. After various experiments, I settled on a simple round-robin scheduling approach. So if you pair three generators A, B, and C, A will get control first, then B takes over when A yields control, then C when B yields control, then A again, and so on.
让每个人都有其他人必须知道的某种ID似乎太麻烦/笨拙,因此他们可以处理其消息或将控制权显式转移到另一个进程。 经过各种实验,我决定采用一种简单的循环调度方法。 因此,如果将三个生成器A,B和C配对,则A将首先获得控制权,然后B在A放弃控制权时接管B,然后C在B放弃控制权时接管,然后再次A,依此类推。
But how should we actually transfer control? Should there be an explicit API for it? Again, after many experiments, I settled on a more implicit approach, which seems to (completely accidentally) be similar to how Koa does it: each generator gets a reference to a shared "token" -- yielding it will signal control-transfer.
但是,我们实际上应该如何转移控制权呢? 是否应该有一个明确的API? 再一次,经过许多实验,我决定采用一种更隐式的方法,这似乎(完全是偶然地)类似于Koa的方法 :每个生成器都引用一个共享的“令牌”- yield该信号将表示控制权转移。
Another issue is what the message channel should look like. On one end of the spectrum you have a pretty formalized communication API like that in core.async and js-csp (put(..) and take(..)). After my own experiments, I leaned toward the other end of the spectrum, where a much less formal approach (not even an API, just a shared data structure like an array) seemed appropriate and sufficient.
另一个问题是消息通道的外观 。 在频谱的一端,您有一个相当正式的通信API,例如core.async和js-csp( put(..)和take(..) )中的API。 经过自己的实验,我趋向于另一端,在这种情况下,一种不那么正式的方法(甚至没有API,只是一个像array这样的共享数据结构)似乎是适当且足够的。
I decided on having an array (called messages) that you can arbitrarily decide how you want to fill/drain as necessary. You can push() messages onto the array, pop() messages off the array, designate by convention specific slots in the array for different messages, stuff more complex data structures in these slots, etc.
我决定使用一个数组(称为messages ),您可以根据需要任意决定如何填充/清空。 您可以push()消息推送到数组上,将pop()消息push()送到数组之外,按约定在数组中指定用于不同消息的特定插槽,在这些插槽中填充更复杂的数据结构,等等。
My suspicion is that some tasks will need really simple message passing, and some will be much more complex, so rather than forcing complexity on the simple cases, I chose not to formalize the message channel beyond it being an array (and thus no API except that of arrays themselves). It's easy to layer on additional formalism to the message passing mechanism in the cases where you'll find it useful (see the state machine example below).
我的怀疑是,有些任务将需要真正简单的消息传递,而有些则要复杂得多,因此,除了使简单的情况变得不复杂之外,我选择不对消息通道进行array化以外的形式来对其进行形式化(因此没有API,除了array本身)。 在您发现有用的情况下,很容易在消息传递机制上附加其他形式主义(请参见下面的状态机示例)。
Finally, I observed that these generator "processes" still benefit from the async capabilities that stand-alone generators can use. In other words, if instead of yielding out the control-token, you yield out a Promise (or asynquence sequence), the runner(..) mechanism will indeed pause to wait for that future value, but will not transfer control -- instead, it will return the result value back to the current process (generator) so it retains control.
最后,我观察到这些生成器“进程”仍然受益于独立生成器可以使用的异步功能 。 换句话说,如果您yield是Promise(或异步序列),而不是yield控制令牌,则Runner runner(..)机制确实会暂停以等待该将来的值,但不会转移控制权 -而是将结果值返回到当前进程(生成器),以便保留控制权。
That last point might be (if I interpret things correctly) the most controversial or unlike the other libraries in this space. It seems that true CSP kind of turns its nose at such approaches. However, I'm finding having that option at my disposal to be very, very useful.
最后一点可能是(如果我正确解释的话)最具争议性或与该领域的其他库不同。 真正的CSP似乎在这种方法上大吃一惊。 但是,我发现可以使用该选项非常非常有用。
Enough theory. Let's just dive into some code:
足够的理论。 让我们深入一些代码:
// Note: omitting fictional `multBy20(..)` and // `addTo2(..)` asynchronous-math functions, for brevity function *foo(token) { // grab message off the top of the channel var value = token.messages.pop(); // 2 // put another message onto the channel // `multBy20(..)` is a promise-generating function // that multiplies a value by `20` after some delay token.messages.push( yield multBy20( value ) ); // transfer control yield token; // a final message from the CSP run yield "meaning of life: " + token.messages[0]; } function *bar(token) { // grab message off the top of the channel var value = token.messages.pop(); // 40 // put another message onto the channel // `addTo2(..)` is a promise-generating function // that adds value to `2` after some delay token.messages.push( yield addTo2( value ) ); // transfer control yield token; }OK, so there's our two generator "processes", *foo() and *bar(). You'll notice both of them are handed the token object (you could call it whatever you want, of course). The messages property on the token is our shared message channel. It starts out filled with the message(s) passed to it from the initialization of our CSP run (see below).
好的,所以我们有两个生成器“进程”, *foo()和*bar() 。 您会注意到它们都交给了token对象(当然,您可以随意命名)。 token上的messages属性是我们的共享消息通道。 它从CSP运行的初始化开始就充满了传递给它的消息(请参见下文)。
yield token explicitly transfers control to the "next" generator (round-robin order). However, yield multBy20(value) and yield addTo2(value) are both yielding promises (from these fictional delayed-math functions), which means that the generator is paused at that moment until the promise completes. Upon promise resolution, the currently-in-control generator picks back up and keeps going.
yield token将控制权明确转移到“下一个”生成器(循环顺序)。 但是, yield multBy20(value)和yield addTo2(value)都在产生promise(来自这些虚构的延迟数学函数),这意味着生成器会在此时暂停直到promise完成。 根据承诺解决方案,当前处于控制状态的生成器将恢复运行并继续运行。
Whatever the final yielded value is, in this case the yield "meaning of... expression statement, that's the completion message of our CSP run (see below).
无论最终yield ed值是什么,在这种情况下yield "meaning of...表达式语句的yield "meaning of... CSP运行的完成消息(请参见下文)。
Now that we have our two CSP process generators, how do we run them? Using asynquence:
现在我们有了两个CSP流程生成器,我们如何运行它们? 使用异步 :
// start out a sequence with the initial message value of `2` ASQ( 2 ) // run the two CSP processes paired together .runner( foo, bar ) // whatever message we get out, pass it onto the next // step in our sequence .val( function(msg){ console.log( msg ); // "meaning of life: 42" } );Obviously, this is a trivial example. But I think it illustrates the concepts pretty well.
显然,这是一个简单的例子。 但我认为它很好地说明了这些概念。
Now might be a good time to go try it yourself (try changing the values around!) to make sure these concepts make sense and that you can code it up yourself!
现在可能是一个不错的时机, 自己动手尝试一下 (尝试更改周围的值!),以确保这些概念有意义并可以自己编写代码!
Let's now examine one of the classic CSP examples, but let's come at it from the simple observations I've made thus far, rather than from the academic-purist perspective it's usually derived from.
现在,让我们研究一下经典的CSP示例之一,但让我们从到目前为止所做的简单观察中,而不是通常从其学术纯粹主义的角度来进行研究。
Ping-pong. What a fun game, huh!? It's my favorite sport.
乒乓球 。 多么有趣的游戏,是吧!? 这是我最喜欢的运动 。
Let's imagine you have implemented code that plays a ping-pong game. You have a loop that runs the game, and you have two pieces of code (for instance, branches in an if or switch statement) that each represent the respective player.
假设您已经实现了玩乒乓球游戏的代码。 您有一个运行游戏的循环,并且有两段代码(例如, if或switch语句中的分支)分别代表相应的玩家。
Your code works fine, and your game runs like a ping-pong champ!
您的代码工作正常,您的游戏像乒乓冠军一样运行!
But what did I observe above about why CSP is useful? Separation of concerns/capabilities. What are our separate capabilities in the ping-pong game? The two players!
但是,对于CSP为什么有用,我在上面观察到了什么? 关注点/能力的分离。 我们在乒乓球比赛中有哪些独立功能? 两位选手!
So, we could, at a very high level, model our game with two "processes" (generators), one for each player. As we get into the details of it, we will realize that the "glue code" that's shuffling control between the two players is a task in and of itself, and this code could be in a third generator, which we could model as the game referee.
因此,我们可以在很高的层次上用两个“进程”(生成器)为我们的游戏建模,每个玩家一个 。 当我们深入研究它的细节时,我们将意识到,在两个玩家之间改组控制的“胶水代码”本身就是一项任务,并且该代码可能在第三个生成器中,我们可以将其建模为游戏裁判 。
We're gonna skip over all kinds of domain-specific questions, like scoring, game mechanics, physics, game strategy, AI, controls, etc. The only part we care about here is really just simulating the back-and-forth pinging (which is actually our metaphor for CSP control-transfer).
我们将跳过所有特定领域的问题,例如计分,游戏机制,物理,游戏策略,AI,控件等。我们在这里只关心的只是模拟来回ping(这实际上是我们对CSP控制传递的隐喻)。
Wanna see the demo? Run it now (note: use a very recent nightly of FF or Chrome, with ES6 JavaScript support, to see generators work)
想看演示吗? 现在运行它 (注意:使用最近有FF或Chrome的每晚版本,并支持ES6 JavaScript,以查看生成器的运行情况)
Now, let's look at the code piece by piece.
现在,让我们逐段看一下代码。
First, what does the asynquence sequence look like?
首先, 异步序列是什么样的?
ASQ( ["ping","pong"], // player names { hits: 0 } // the ball ) .runner( referee, player, player ) .val( function(msg){ message( "referee", msg ); } );We set up our sequence with two initial messages: ["ping","pong"] and { hits: 0 }. We'll get to those in a moment.
我们用两个初始消息设置了序列: ["ping","pong"]和{ hits: 0 } 。 我们一会儿再谈。
Then, we set up a CSP run of 3 processes (coroutines): the *referee() and two *player() instances.
然后,我们设置了一个由3个进程(协程)组成的CSP运行: *referee()和两个*player()实例。
The final message at the end of the game is passed along to the next step in our sequence, which we then output as a message from the referee.
游戏结束时的最终消息将传递到我们序列中的下一步,然后作为裁判的消息输出。
The implementation of the referee:
裁判的执行:
function *referee(table){ var alarm = false; // referee sets an alarm timer for the game on // his stopwatch (10 seconds) setTimeout( function(){ alarm = true; }, 10000 ); // keep the game going until the stopwatch // alarm sounds while (!alarm) { // let the players keep playing yield table; } // signal to players that the game is over table.messages[2] = "CLOSED"; // what does the referee say? yield "Time's up!"; }I've called the control-token table to match the problem domain (a ping-pong game). It's a nice semantic that a player "yields the table" to the other when he hits the ball back, isn't it?
我称控制令牌table与问题域匹配(乒乓球游戏)。 这是一个很好的语义,即当一名球员将球击回时,他会“屈服于对方”,不是吗?
The while loop in *referee() just keeps yielding the table back to the players as long as his alarm on his stopwatch hasn't gone off. When it does, he takes over and declares the game over with "Time's up!".
只要他的秒表上的闹钟没有响起, *referee()的while循环就会一直将table退还给玩家。 完成后,他接管游戏,并用"Time's up!"宣布比赛结束"Time's up!" 。
Now, let's look at the *player() generator (which we use two instances of):
现在,让我们看一下*player()生成器(我们使用了两个实例):
function *player(table) { var name = table.messages[0].shift(); var ball = table.messages[1]; while (table.messages[2] !== "CLOSED") { // hit the ball ball.hits++; message( name, ball.hits ); // artificial delay as ball goes back to other player yield ASQ.after( 500 ); // game still going? if (table.messages[2] !== "CLOSED") { // ball's now back in other player's court yield table; } } message( name, "Game over!" ); }The first player takes his name off the first message's array ("ping"), then the second player takes his name ("pong"), so they can both identify themselves properly. Both players also keep a reference to the shared ball object (with its hits counter).
第一个玩家将其名字从第一个消息的数组( "ping" ) "ping" ,然后第二个玩家将其名称( "pong" ) "pong" ,以便他们都可以正确地标识自己。 两名球员还保留对共享ball对象的引用(带有其hits计数器)。
While the players haven't yet heard the closing message from the referee, they "hit" the ball by upping its hits counter (and outputting a message to announce it), then they wait for 500 ms (just to fake the ball not traveling at the speed of light!).
当球员尚未听到裁判员的闭幕消息时,他们通过增加hits计数器“击中” ball (并输出一条消息宣布该球),然后等待500毫秒(只是为了假球未移动)以光速!)。
If the game is still going, they then "yield the table" back to the other player.
如果游戏仍在进行,则他们“屈服桌子”给另一位玩家。
That's it!
而已!
Take a look at the demo's code to get a complete in-context code listing to see all the pieces working together.
查看该演示的代码,以获取完整的上下文内代码清单,以查看所有这些部分协同工作。
One last example: defining a state machine as a set of generator coroutines that are driven by a simple helper.
最后一个示例:将状态机定义为一组由简单助手驱动的生成程序协程。
Demo (note: use a very recent nightly of FF or Chrome, with ES6 JavaScript support, to see generators work)
演示 (请注意:使用FF或Chrome的最新版本,并支持ES6 JavaScript,以查看生成器的工作情况)
First, let's define a helper for controlling our finite state handlers:
首先,让我们定义一个用于控制有限状态处理程序的助手:
function state(val,handler) { // make a coroutine handler (wrapper) for this state return function*(token) { // state transition handler function transition(to) { token.messages[0] = to; } // default initial state (if none set yet) if (token.messages.length < 1) { token.messages[0] = val; } // keep going until final state (false) is reached while (token.messages[0] !== false) { // current state matches this handler? if (token.messages[0] === val) { // delegate to state handler yield *handler( transition ); } // transfer control to another state handler? if (token.messages[0] !== false) { yield token; } } }; }This state(..) helper utility creates a delegating-generator wrapper for a specific state value, which automatically runs the state machine, and transfers control at each state transition.
这个state(..)帮助程序实用程序为特定的状态值创建了一个委托生成器包装器,该包装器自动运行状态机,并在每次状态转换时转移控制权。
Purely by convention, I've decided the shared token.messages[0] slot will hold the current state of our state machine. That means you can seed the initial state by passing in a message from the previous sequence step. But if no such initial message is passed along, we simply default to the first defined state as our initial state. Also, by convention, the final terminal state is assumed to be false. That's easy to change as you see fit.
纯粹按照惯例,我已经决定了共享token.messages[0]插槽将保存我们状态机的当前状态。 这意味着您可以通过传递来自上一个序列步骤的消息来播种初始状态。 但是,如果没有传递此类初始消息,则我们将默认默认为第一个定义的状态作为初始状态。 同样,按照惯例,最终的终端状态假定为false 。 您认为合适时,很容易更改。
State values can be whatever sort of value you'd like: numbers, strings, etc. As long as the value can be strict-tested for equality with a ===, you can use it for your states.
状态值可以是您想要的任何类型的值: number s, string s等。只要可以使用===严格测试该值的相等性,就可以将其用于状态。
In the following example, I show a state machine that transitions between four number value states, in this particular order: 1 -> 4 -> 3 -> 2. For demo purposes only, it also uses a counter so that it can perform the transition loop more than once. When our generator state machine finally reaches the terminal state (false), the asynquence sequence moves onto the next step, just as you'd expect.
在以下示例中,我示出了状态机的四个之间的过渡number值的状态,在这个特定的顺序: 1 -> 4 -> 3 -> 2 。 仅出于演示目的,它还使用一个计数器,以便它可以多次执行转换循环。 当我们的生成器状态机最终达到终端状态( false )时, 异步序列就移到了下一步,正如您所期望的那样。
// counter (for demo purposes only) var counter = 0; ASQ( /* optional: initial state value */ ) // run our state machine, transitions: 1 -> 4 -> 3 -> 2 .runner( // state `1` handler state( 1, function*(transition){ console.log( "in state 1" ); yield ASQ.after( 1000 ); // pause state for 1s yield transition( 4 ); // goto state `4` } ), // state `2` handler state( 2, function*(transition){ console.log( "in state 2" ); yield ASQ.after( 1000 ); // pause state for 1s // for demo purposes only, keep going in a // state loop? if (++counter < 2) { yield transition( 1 ); // goto state `1` } // all done! else { yield "That's all folks!"; yield transition( false ); // goto terminal state } } ), // state `3` handler state( 3, function*(transition){ console.log( "in state 3" ); yield ASQ.after( 1000 ); // pause state for 1s yield transition( 2 ); // goto state `2` } ), // state `4` handler state( 4, function*(transition){ console.log( "in state 4" ); yield ASQ.after( 1000 ); // pause state for 1s yield transition( 3 ); // goto state `3` } ) ) // state machine complete, so move on .val(function(msg){ console.log( msg ); });Should be fairly easy to trace what's going on here.
应该很容易跟踪这里发生的事情。
yield ASQ.after(1000) shows these generators can do any sort of promise/sequence based async work as necessary, as we've seen earlier. yield transition(..) is how we transition to a new state.
yield ASQ.after(1000)表明这些生成器可以根据需要执行任何类型的基于yield ASQ.after(1000) / Sequence的异步工作,如我们之前所见。 yield transition(..)是我们过渡到新状态的方式。
Our state(..) helper above actually does the hard work of handling the yield* delegation and transition juggling, leaving our state handlers to be expressed in a very simple and natural fashion.
上面的state(..)助手实际上完成了yield*委派和过渡变戏法的艰苦工作 ,使我们的状态处理程序以非常简单自然的方式表示。
The key to CSP is joining two or more generator "processes" together, giving them a shared communication channel, and a way to transfer control between each other.
CSP的关键是将两个或多个生成器“进程”结合在一起,为它们提供共享的通信通道,以及在彼此之间传递控制的方式。
There are a number of libraries that have more-or-less taken a fairly formal approach in JS that matches Go and Clojure/ClojureScript APIs and/or semantics. All of these libraries have really smart developers behind them, and they all represent great resources for further investigation/exploration.
在JS中,有一些库或多或少地采用了一种相当正式的方法来匹配Go和Clojure / ClojureScript API和/或语义。 所有这些库背后都有真正精明的开发人员,它们都代表了进行进一步调查/探索的强大资源。
asynquence tries to take a somewhat less-formal approach while hopefully still preserving the main mechanics. If nothing else, asynquence's runner(..) makes it pretty easy to start playing around with CSP-like generators as you experiment and learn.
异步尝试采取一种不太正规的方法,同时希望仍然保留主要机制。 如果没有其他问题, 那么在您进行实验和学习时, asynquence的Runner runner(..)使其非常容易开始使用类似CSP的生成器 。
The best part though is that asynquence CSP works inline with the rest of its other async capabilities (promises, generators, flow control, etc). That way, you get the best of all worlds, and you can use whichever tools are appropriate for the task at hand, all in one small lib.
不过,最好的部分是异步 CSP 与其其余的 其他异步功能 (承诺,生成器,流控制等)一起内联工作。 这样一来,您就可以发挥出所有优势,并且可以在一个小型库中使用适合手头任务的任何工具。
Now that we've explored generators in quite a bit of detail over these last four posts, my hope is that you're excited and inspired to explore how you can revolutionize your own async JS code! What will you build with generators?
既然我们已经在最后四篇文章中详细探讨了生成器,我希望您很兴奋并受到启发,探索如何革新自己的异步JS代码! 您将使用发电机来构建什么?
翻译自: https://davidwalsh.name/concurrent-generators
相关资源:jdk-8u281-windows-x64.exe