Beyond the Callback: Yield Control with Javascript Generators

68
BEYOND THE CALLBACK: Yield Control with Javascript Generators JuLy 16, 2015 Darren Cruse [email protected]

Transcript of Beyond the Callback: Yield Control with Javascript Generators

Page 1: Beyond the Callback: Yield Control with Javascript Generators

BEYOND THE CALLBACK:Yield Control with Javascript Generators

JuLy 16, 2015

Darren Cruse [email protected]

Page 2: Beyond the Callback: Yield Control with Javascript Generators

STORY TIME…(put on your jammies)

Page 3: Beyond the Callback: Yield Control with Javascript Generators

Once upon a time…

Page 4: Beyond the Callback: Yield Control with Javascript Generators

When you were young

• And learning to program at your momma’s knee…

Page 5: Beyond the Callback: Yield Control with Javascript Generators

She said

• Break your big long sections of code into little functions that do just one thing each

Page 6: Beyond the Callback: Yield Control with Javascript Generators

Let the main routine call the little ones

• And they will each run

• Until they’re done

• And return to the main function

Page 7: Beyond the Callback: Yield Control with Javascript Generators

This is called “Control Flow”

• Can you say “control flow”?

Page 8: Beyond the Callback: Yield Control with Javascript Generators

(I knew you could)

Page 9: Beyond the Callback: Yield Control with Javascript Generators

And then one day…

• (maybe in Colorado)

Page 10: Beyond the Callback: Yield Control with Javascript Generators

Momma said

• How about we try something new?

Page 11: Beyond the Callback: Yield Control with Javascript Generators

Instead of that function you’ve been used to…

• The one that starts at the top

• And executes till it’s done

• And then just returns

Page 12: Beyond the Callback: Yield Control with Javascript Generators

Let’s have us a function

• That’s starts at the beginning like normal

Page 13: Beyond the Callback: Yield Control with Javascript Generators

• But returns from the middle

• Not when it’s done done, but maybe when it’s just part way done

Page 14: Beyond the Callback: Yield Control with Javascript Generators

• But if we call it again

Page 15: Beyond the Callback: Yield Control with Javascript Generators

• Let’s have it start up in the middle again

Page 16: Beyond the Callback: Yield Control with Javascript Generators

• And then we can have it do some stuff

• And then maybe return from the middle again

Page 17: Beyond the Callback: Yield Control with Javascript Generators

• So when we call it again

Page 18: Beyond the Callback: Yield Control with Javascript Generators

• It can start up in the middle again

Page 19: Beyond the Callback: Yield Control with Javascript Generators

• And then maybe we could do this three or four times

Page 20: Beyond the Callback: Yield Control with Javascript Generators

• And maybe throw some loops in there

Page 21: Beyond the Callback: Yield Control with Javascript Generators

• And then eventually when the function is really “done done”

• it can return

• And be finished

• Like we’re used to with our other functions

Page 22: Beyond the Callback: Yield Control with Javascript Generators

So what do you think About this new kind of function?

Page 23: Beyond the Callback: Yield Control with Javascript Generators

Sound good?

Page 24: Beyond the Callback: Yield Control with Javascript Generators
Page 25: Beyond the Callback: Yield Control with Javascript Generators

But wait a minute

Page 26: Beyond the Callback: Yield Control with Javascript Generators

Was your momma maybe kind of an academic type?

Page 27: Beyond the Callback: Yield Control with Javascript Generators

Did she also use words like…

“lazy sequences” !

“coroutines” !

“delimited continuations” !

“callcc”

Page 28: Beyond the Callback: Yield Control with Javascript Generators

If so…

Page 29: Beyond the Callback: Yield Control with Javascript Generators

Then yo momma wasn’t on “the pipe”.

Page 30: Beyond the Callback: Yield Control with Javascript Generators

Yo momma was talking about

Page 31: Beyond the Callback: Yield Control with Javascript Generators

Generator Functions!!!

Page 32: Beyond the Callback: Yield Control with Javascript Generators

(as added by the ecmascript 6 standard)

Page 33: Beyond the Callback: Yield Control with Javascript Generators

And yo momma deserves credit

Page 34: Beyond the Callback: Yield Control with Javascript Generators

(as does the es6 committee)

Page 35: Beyond the Callback: Yield Control with Javascript Generators

For trying to teach your sorry a** about alternative means of

control flow…

Page 36: Beyond the Callback: Yield Control with Javascript Generators

And thereby save you from the fate of heavyweight

multithreading that doesn’t scale, and shared state concurrency with

rampant race conditions

Page 37: Beyond the Callback: Yield Control with Javascript Generators

Which is the true…

Page 38: Beyond the Callback: Yield Control with Javascript Generators

Enough silliness

Page 39: Beyond the Callback: Yield Control with Javascript Generators

In computer science, a generator is a special routine that can be used to control the iteration behaviour of a loop. In fact, all generators are iterators.[1] A generator is very similar to a

function that returns an array, in that a generator has parameters, can be called, and generates a sequence of values.

However, instead of building an array containing all the values and returning them all at once, a generator yields the values one at a time, which requires less memory and allows the caller to get started processing the first few values immediately. In short,

a generator looks like a function but behaves like an iterator.

From wikipedia:

https://en.wikipedia.org/wiki/Generator_(computer_programming)

Page 40: Beyond the Callback: Yield Control with Javascript Generators

2.1 Lisp 2.2 CLU 2.3 Icon 2.4 C++ 2.5 Perl 2.6 Tcl

2.7 Haskell 2.8 Racket 2.9 PHP 2.10 Ruby 2.11 Java 2.12 C# 2.13 XL 2.14 F#

2.15 Python

Also from wikipedia:I was surprised to see the number of languages with generators (not all are “native” though)

Page 41: Beyond the Callback: Yield Control with Javascript Generators

The typical generator example is very often…

An infinite Fibonacci Sequence

function* fibonacci() { let [prev, curr] = [0, 1]; while (true) { yield curr; [prev, curr] = [curr, prev + curr]; } } !var gen = fibonacci(); console.log(gen.next().value); // 1 console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 console.log(gen.next().value); // 3 console.log(gen.next().value); // 5 console.log(gen.next().value); // 8

again from: https://en.wikipedia.org/wiki/Generator_(computer_programming)

Page 42: Beyond the Callback: Yield Control with Javascript Generators

“yield” indicates to yield the value “curr” to the caller. This is

also where the function will resume next time.

Picking it apart…

function* fibonacci() { let [prev, curr] = [0, 1]; while (true) { yield curr; [prev, curr] = [curr, prev + curr]; } }

again from: https://en.wikipedia.org/wiki/Generator_(computer_programming)

The “*” in “function*” indicates this is a generator function

Page 43: Beyond the Callback: Yield Control with Javascript Generators

Each call to “gen.next()” runs the generator till

the next yield (or till the function returns), and

gets back an object containing the yielded

(or returned) value.

Picking it apart…

var gen = fibonacci(); console.log(gen.next().value); // 1 console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 console.log(gen.next().value); // 3 console.log(gen.next().value); // 5 console.log(gen.next().value); // 8

again from: https://en.wikipedia.org/wiki/Generator_(computer_programming)

Calling the function does not run the function body. Rather it’s like calling a constructor. It returns a generator object that you can then use to run (and resume) the function body.

Page 44: Beyond the Callback: Yield Control with Javascript Generators

{ value: val, done: true }

What gen.next() returns is:

A little object like:

“value” is the value yielded/returned

“done” is true if the generator “returned” (or exited without a “return”).

as opposed to a yield.

once a generator is “done” it should not be “next’ed” (if so it simply returns undefined)

Page 45: Beyond the Callback: Yield Control with Javascript Generators

But add the --harmony option on the node command!!

node > v0.11.2

Recent versions of Chrome Recent versions of Firefox

We can run this code as it is in

Also in the browser in:

For other browsers a transpiler is required…

io.js supports it (by default - no option needed)

Page 46: Beyond the Callback: Yield Control with Javascript Generators

The most commonly mentioned transpiler for generators is from Facebook and is called

“Regenerator”

https://www.npmjs.com/package/regenerator

npm install -g regenerator regenerator --include-runtime fibonacci-basic.js > fibonacci-basic-es5.js

Page 47: Beyond the Callback: Yield Control with Javascript Generators

Regenerator works by converting your code to a state machine…

function fibonacci() { var prev, curr; ! return regeneratorRuntime.wrap( function fibonacci$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: prev = 0; curr = 1; case 2: if (!true) { context$1$0.next = 9; break; } context$1$0.next = 5; return curr; case 5: prev = curr; curr = prev + curr; context$1$0.next = 2; break; case 9: case "end": return context$1$0.stop(); } }, marked0$0[0], this); }

Page 48: Beyond the Callback: Yield Control with Javascript Generators

babel has support as well (that uses Regenerator to do the magic)

!

Traceur supports them as well.

Page 49: Beyond the Callback: Yield Control with Javascript Generators

Even though there’s fibonacci examples everywhere…

• What I found interesting was that almost immediately after generators came on the scene there was a spate of “task runners”.

• These are utilities that invert the relationship of consumer and producer like you see in the fibonacci example

• In order to simplify asynchronous code where it looks like it’s “blocking”

Page 50: Beyond the Callback: Yield Control with Javascript Generators

Lots-o-Task-Runners

• co (TJ/Visionmedia)

• Q.async (Q)

• Promise.coroutine (bluebird)

• galaxy

• monocle

• suspend

• genny

• gen-run

• coro

• (and on…)

Page 51: Beyond the Callback: Yield Control with Javascript Generators

“Paving the cow paths?”

(how about a stampede!!)

Page 52: Beyond the Callback: Yield Control with Javascript Generators

Probably so many because:

• 1. People wanted them

• 2. They’re pretty easy to implement once you have generators.

Page 53: Beyond the Callback: Yield Control with Javascript Generators

Remember when you first learned to program how simple it was…

var myNumber = parseInt(Math.random() * 100); !while (true) { var guess = read.question('What is your guess? '); if (guess > myNumber) { console.log('Guess lower.'); } else if (guess < myNumber) { console.log('Guess higher.'); } else { console.log('YOU WIN!'); break; } }

Page 54: Beyond the Callback: Yield Control with Javascript Generators

There are cases when blocking code really is easier to follow

var myNumber = parseInt(Math.random() * 100); !rl.setPrompt('What is your guess? '); rl.prompt(); !rl.on('line', function(guess) { if (guess > myNumber) { console.log('Guess lower.'); } else if (guess < myNumber) { console.log('Guess higher.'); } else { console.log('YOU WIN!'); rl.close(); } rl.prompt(); }).on('close', function() { console.log('Thanks for playing.'); process.exit(0); });

Page 55: Beyond the Callback: Yield Control with Javascript Generators

Looking at “co” as an example of a “task runner”…

var myNumber = parseInt(Math.random() * 100); !co(function* playgame() { while(true) { var guess = yield prompt('What is your guess? '); if (guess > myNumber) { console.log('Guess lower.'); } else if (guess < myNumber) { console.log('Guess higher.'); } else { console.log('YOU WIN!'); return "Goodbye"; } } })

Page 56: Beyond the Callback: Yield Control with Javascript Generators

To clarify - when we used fibonacci we were the consumer of the generator

function* fibonacci() { let [prev, curr] = [0, 1]; while (true) { yield curr; [prev, curr] = [curr, prev + curr]; } } !var gen = fibonacci(); console.log(gen.next().value); // 1 console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 console.log(gen.next().value); // 3 console.log(gen.next().value); // 5 console.log(gen.next().value); // 8

“library” code

“our” code

(=“producer”)

(=“consumer”)

Page 57: Beyond the Callback: Yield Control with Javascript Generators

Here things are reversed

var myNumber = parseInt(Math.random() * 100); !co(function* playgame() { while(true) { var guess = yield prompt('What is your guess?'); ! if (guess > myNumber) { console.log('Guess lower.'); } else if (guess < myNumber) { console.log('Guess higher.'); } else { console.log('YOU WIN!'); return "Goodbye"; } } })

We’ve written the “producing” (yielding) code, and hidden inside the “co” routine is the gen.next() “consuming” code.

In this case “prompt” would typically return a promise which is yielded back to “co”, then “co”

will “gen.next()” us once the promise

resolves to a value. !

This is the “secret sauce” for appearing to block yet remaining single threaded as always.

Page 58: Beyond the Callback: Yield Control with Javascript Generators

AN EASY METAPHOR…

We “yield” the control flow and let other stuff (like the event loop)

run until the asynchronous result is ready

Page 59: Beyond the Callback: Yield Control with Javascript Generators

But there’s another “metaphor” coming in es7…

Async/Await

Page 60: Beyond the Callback: Yield Control with Javascript Generators

Basically the story (more or less) is…There was a strawman proposal for async/await

at the same time there was a proposal for generators !

The generators proposal got approved in time for es6 !

The async/await proposal did not but is part of es7 !

The good news is async/await is pretty much the same idea as “task runners” but with slightly simpler syntax

!The syntax is known for being similar to what C# has had - it doesn’t have e.g. function* is has async/await

!(though some javascript async/await implementations are

using generators internally btw) !

Also the async/await standard is designed to complement the new promise standards i.e. to work

well with apis returning promises.

Page 61: Beyond the Callback: Yield Control with Javascript Generators

Same control flow - nicer syntax:var prompt = require('./node_modules/prompt-promise'); !var myNumber = parseInt(Math.random() * 100); !// note instead of function* below we have "async" async function playgame() { try { var gameover = false; while(!gameover) { // and instead of yield we "await" var guess = await prompt('What is your guess? '); ! if (guess > myNumber) { console.log('Guess lower.'); } else if (guess < myNumber) { console.log('Guess higher.'); } else { console.log('YOU WIN!'); gameover = true; } } console.log('Thanks for playing.'); } catch(err) { console.log(err); } }

Page 62: Beyond the Callback: Yield Control with Javascript Generators

Yay Working Exceptions!!

• Yes those were standard try/catch blocks in that last example!!

• Yay (standard) exceptions work even with asynchronous code!!

• (no special .catch etc. as with promises)

• They work both with the generator “task runners” and with async/await.

• I didn’t mention but in addition to gen.next() there’s also a “gen.throw()” which throws an exception at the point the generator has paused on a “yield”. This is the secret sauce.

Page 63: Beyond the Callback: Yield Control with Javascript Generators

SUPER BRIEFLY: SOME INTERESTING USES

OF GENERATORS

Page 64: Beyond the Callback: Yield Control with Javascript Generators

Briefly: KOA (http://koajs.com)

• Another TJ/VisionMedia Project (as is “co”)

• Intended as the next generation of “Express” (which TJ/VisionMedia also developed)

• It’s been able to simplify the writing of middleware greatly since you simply “yield” to the downstream middleware:

app.use(function *(next){ var start = new Date; yield next; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); });

get the time before

yield to downstream

get the time after

Page 65: Beyond the Callback: Yield Control with Javascript Generators

Briefly: js-csp

• A “communicating sequential processes” library in javascript - it uses generators in it’s implementation.

• Modeled off of core.async and google go.

• A key concept is that concurrent processes communicate through channels

• And the default behavior of channels is to block (they can buffer if you want but the default is to block).

• Think of the simplicity like you see in our “guess this number” example, but waiting on concurrent processes as opposed to user input.

• See also:

• https://github.com/ubolonton/js-csp

• http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-JavaScript

• http://swannodette.github.io/2013/08/24/es6-generators-and-csp/

Page 66: Beyond the Callback: Yield Control with Javascript Generators

Briefly: js-csp

var {chan,timeout} = csp = require(‘js-csp'); !var ch = chan(); !go { var val; while((val = <- ch) !== csp.CLOSED) { console.log(val); } } !go { ch <- 1; (<- timeout(1000)); ch <- 2; ch.close(); }

This example uses js-csp with sweet.js macros I based on some started by James Long. Note <- is a blocking channel operation (i.e. it’s doing a yield behind it)

a channel to talk over

run this reader guy

concurrently with

this writer guy

wait for the writer

wait for the reader

Page 67: Beyond the Callback: Yield Control with Javascript Generators

js-csp in the browser (w/out sweet macros this time) (thanks David Nolen & James Long)

• How about being able to block on a channel of events? Really changes how you think about UI programming!!

function listen(el, type) { var ch = chan(); el.addEventListener(type, function(e) { csp.putAsync(ch, e); }); return ch; } !go(function*() { var el = document.querySelector('#ui1'); var ch = listen(el, 'mousemove'); while(true) { var e = yield take(ch); el.innerHTML = ((e.layerX||e.clientX)+', ' + (e.layerY || e.clientY)); } });

create channel of “type” events on “el”

concurrent with other stuff happening

on the page

wait for mouse moves and display

them

http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-JavaScript

Page 68: Beyond the Callback: Yield Control with Javascript Generators

THANKS FOR COMING