Created
June 23, 2019 23:56
-
-
Save gaswelder/d9a520c9a846daad8705046f7da242b4 to your computer and use it in GitHub Desktop.
Coroutines emulation in Javascript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This is an example of how cooperative multitasking could be | |
| // emulated in Javascript. | |
| // Javascript has only generators, which are "half-coroutines", | |
| // meaning they can only yield to their called and not other | |
| // generators. We can add a special "coordinator" generator and | |
| // a yielding convention to make it look almost exactly like | |
| // we have full coroutines. | |
| // Note that the yield* operator is not what we need here. | |
| // The yield* operator creates a new instance of a generator | |
| // instead of jumping to an existing generator. | |
| const queue = []; | |
| main(); | |
| /** | |
| * main simply creates a coordinating routine and runs it. | |
| */ | |
| function main() { | |
| for (const _ of cooperate(producer, consumer)); | |
| } | |
| /** | |
| * cooperate serves as a coordinating generator for other generators, | |
| * allowing them to behave like coroutines. | |
| * | |
| * The list of the generators must be known upfront. | |
| * Every such "cogenerator" has to yield the pointer of the next generator | |
| * to be called. | |
| * | |
| * @param {...any} generators List of generator functions | |
| */ | |
| function* cooperate(...generators) { | |
| // A generator -> iterator map. | |
| const procs = new Map(generators.map(g => [g, g()])); | |
| // Start with "any" routine. | |
| let next = generators[0]; | |
| while (true) { | |
| // Call the routine. The routine will do its work | |
| // and yield a pointer to the generator function | |
| // the routine of which we should call next. | |
| const e = procs.get(next).next(); | |
| // Assume e.done is always false. | |
| if (e.done) { | |
| throw new Error("hmmm"); | |
| } | |
| next = e.value; | |
| // Before jumping to the next routine yield to the function | |
| // that called this scheduler so that it could do some work | |
| // of its own. | |
| yield; | |
| } | |
| } | |
| function* producer() { | |
| while (true) { | |
| while (queue.length < 10) { | |
| const x = Math.random(); | |
| queue.push(x); | |
| console.log("produced", x); | |
| } | |
| yield consumer; | |
| } | |
| } | |
| function* consumer() { | |
| while (true) { | |
| while (queue.length > 0) { | |
| const x = queue.shift(); | |
| console.log("consumed", x); | |
| } | |
| yield producer; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment