What Tamagotchis can teach you about ES6 generators

A presentation at FrontConf in April 2019 in Munich, Germany by Jenn Creighton

Slide 1

Slide 1

What Tamagotchis can teach you about ES6 generators

Slide 2

Slide 2

Hi, I’m Jenn. Frontend Engineer @gurlcode

Slide 3

Slide 3

@gurlcode

Slide 4

Slide 4

SVG @gurlcode

Slide 5

Slide 5

SVG Controls @gurlcode

Slide 6

Slide 6

SVG Canvas Controls @gurlcode

Slide 7

Slide 7

Canvas var canvas = document.querySelector(‘canvas’); var context = canvas.context(‘2d’); context.drawImage(‘tamagotchi.png’, …); @gurlcode

Slide 8

Slide 8

Animations context.drawImage(image, 0, …coordinates); context.clear(); context.drawImage(image, 200, …coordinates); context.clear(); context.drawImage(image, 400, …coordinates);

Slide 9

Slide 9

Animations context.drawImage(image, 0, …coordinates); context.clear(); context.drawImage(image, 200, …coordinates); context.clear(); context.drawImage(image, 400, …coordinates);

Slide 10

Slide 10

Animations context.drawImage(image, 0, …coordinates); context.clear(); context.drawImage(image, 200, …coordinates); context.clear(); context.drawImage(image, 400, …coordinates);

Slide 11

Slide 11

Slide 12

Slide 12

Slide 13

Slide 13

function bounce(x) { return () => { context.drawImage(…, x, …); context.clear(); } } setTimeout(bounce(0), 300); setTimeout(bounce(200), 600); setTimeout(bounce(400), 900); setTimeout(bounce(200), 1200); setTimeout(bounce(0), 1500);

Slide 14

Slide 14

I want to resolve an animation, then handle another animation. var promise = new Promise((resolve, reject) => { …things resolve(value); }; promise().then((value) => …more_things); @gurlcode

Slide 15

Slide 15

Animation with Promises function animate(draw, ms) { return new Promise((resolve, reject) => { var animation = () => { context.clear(); var isComplete = draw(resolve); } } if (!isComplete) { setTimeout(() => (animation), ms); } animation(); }); @gurlcode

Slide 16

Slide 16

Animation with Promises function bounceUp() { var frameWidth = 200; var frameCount = 2; var currentFrame = 0; } return animate((resolve) => { context.drawImage(frameWidth * currentFrame, …); context.clear(); currentFrame++; if (currentFrame > frameCount) { resolve(); return true; } }, ms); @gurlcode

Slide 17

Slide 17

function bounce() { return bounceUp .then(bounceDown); } bounce() .then(bounce); .then(bounce); @gurlcode

Slide 18

Slide 18

function loop() { return idle() .then(loop); } @gurlcode

Slide 19

Slide 19

Except… we got problems @gurlcode

Slide 20

Slide 20

Thenable Hell bounce(() => { moveRight(() => { moveLeft(() => { // Am I in hell? }); }); })

Slide 21

Slide 21

Thenable Hell bounce() bounce(() => { moveRight(() => { moveLeft(() => { // Am I in hell? }); }); }) .then(() => moveRight(20)) .then(() => moveLeft(40)) .then(() => { var shouldFeed = … if () {… else { … }) .then(bounce) .then( // Still in hell. )

Slide 22

Slide 22

var pending = []; function loop() { if (pending.length) { function handleEvent() { return handleEvent(); var event = pending.shift(); } event().then(loop); } return tamagotchi.idle() .then(loop); } @gurlcode

Slide 23

Slide 23

var pending = []; function loop() { if (pending.length) { function handleEvent() { handleEvent(); var event = pending.shift(); } event().then(loop); } return tamagotchi.idle() .then(loop); } @gurlcode

Slide 24

Slide 24

Slide 25

Slide 25

Promises are Unbreakable Vows @gurlcode

Slide 26

Slide 26

I want to pause an animation, & yield to an event. function* generator() { // pause } @gurlcode

Slide 27

Slide 27

Generators 101 @gurlcode

Slide 28

Slide 28

function* count() { yield 1; yield; yield 3; } @gurlcode

Slide 29

Slide 29

function* count() { yield 1; yield; yield 3; } var generator = count(); Generator {}; @gurlcode

Slide 30

Slide 30

function* count() { yield 1; yield; yield 3; } var generator = count(); Generator {}; generator.next(); { value: 1, done: false } @gurlcode

Slide 31

Slide 31

function* count() { yield 1; yield; yield 3; } var generator = count(); Generator {}; generator.next(); { value: 1, done: false } generator.next(); { value: undefined, done: false } generator.next(); { value: 3, done: false } generator.next(); { value: undefined, done: true } @gurlcode

Slide 32

Slide 32

function* count() { yield 1; yield; yield 3; } var generator = count(); Generator {}; generator.next(); { value: 1, done: false } generator.next(); { value: undefined, done: false } generator.next(); { value: 3, done: false } generator.next(); { value: undefined, done: true } @gurlcode

Slide 33

Slide 33

function* count() { yield 1; yield; yield 3; } var generator = count(); Generator {}; generator.next(); { value: 1, done: false } generator.next(); { value: undefined, done: false } generator.next(); { value: 3, done: false } generator.next(); { value: undefined, done: true } @gurlcode

Slide 34

Slide 34

function* add() { const num = yield; yield 2 + num; yield 4 + num; } var generator = add(); Generator {}; generator.next(); { value: undefined, done: false } generator.next(2); { value: 4, done: false } generator.next(); { value: 6, done: false } generator.next(); { value: undefined, done: true } @gurlcode

Slide 35

Slide 35

function* add() { const num = yield; yield 2 + num; yield 4 + num; } var generator = add(); Generator {}; generator.next(); { value: undefined, done: false } generator.next(2); { value: 4, done: false } generator.next(); { value: 6, done: false } generator.next(); { value: undefined, done: true } @gurlcode

Slide 36

Slide 36

function* add() { const num = yield; yield 2 + num; yield 4 + num; } var generator = add(); Generator {}; generator.next(); { value: undefined, done: false } generator.next(2); { value: 4, done: false } generator.next(); { value: 6, done: false } generator.next(); { value: undefined, done: true } @gurlcode

Slide 37

Slide 37

function* add() { const num = yield; yield 2 + num; yield 4 + num; } var generator = add(); Generator {}; generator.next(); { value: undefined, done: false } generator.next(2); { value: 4, done: false } generator.next(); { value: 6, done: false } generator.next(); { value: undefined, done: true } @gurlcode

Slide 38

Slide 38

Infinite Generators @gurlcode

Slide 39

Slide 39

function* forever(num) { while (true) { yield 2 + num; } } var generator = forever(2); Generator {}; @gurlcode

Slide 40

Slide 40

function* forever(num) { while (true) { num = yield 2 + num; } } var generator = forever(2); Generator {}; generator.next(); { value: 4, done: false } generator.next(2); { value: 6, done: false } generator.next(2); { value: 8, done: false } generator.next(2); { value: 10, done: false } { value: …, done: false } @gurlcode

Slide 41

Slide 41

yield* @gurlcode

Slide 42

Slide 42

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } var generator = outer(); Generator {}; @gurlcode

Slide 43

Slide 43

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } var generator = outer(); Generator {}; @gurlcode

Slide 44

Slide 44

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } var generator = outer(); Generator {}; @gurlcode

Slide 45

Slide 45

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } var generator = outer(); Generator {}; @gurlcode

Slide 46

Slide 46

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } generator.next(); { value: 1, done: false } generator.next(); { value: “a”, done: false } generator.next(); { value: “b”, done: false } generator.next(); { value: 2, done: false } generator.next(); { value: undefined, done: true }

Slide 47

Slide 47

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } generator.next(); { value: 1, done: false } generator.next(); { value: “a”, done: false } generator.next(); { value: “b”, done: false } generator.next(); { value: 2, done: false } generator.next(); { value: undefined, done: true }

Slide 48

Slide 48

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } generator.next(); { value: 1, done: false } generator.next(); { value: “a”, done: false } generator.next(); { value: “b”, done: false } generator.next(); { value: 2, done: false } generator.next(); { value: undefined, done: true }

Slide 49

Slide 49

function* outer() { yield 1; yield* inner(); yield 2; } function* inner() { yield “a”; yield “b”; } generator.next(); { value: 1, done: false } generator.next(); { value: “a”, done: false } generator.next(); { value: “b”, done: false } generator.next(); { value: 2, done: false } generator.next(); { value: undefined, done: true }

Slide 50

Slide 50

Canceling a Generator @gurlcode

Slide 51

Slide 51

function* forever(num) { while (true) { yield 2 + num; return; } } var generator = forever(2); Generator {}; generator.next(); { value: 4, done: false } generator.next(); { value: undefined, done: true } generator.next(); { value: undefined, done: true } @gurlcode

Slide 52

Slide 52

function* forever(num) { while (true) { yield 2 + num; return; } } var generator = forever(2); Generator {}; generator.next(); { value: 4, done: false } generator.next(); { value: undefined, done: true } generator.next(); { value: undefined, done: true } @gurlcode

Slide 53

Slide 53

function* forever(num) { while (true) { yield 2 + num; return; } } var generator = forever(2); Generator {}; generator.next(); { value: 4, done: false } generator.next(); { value: undefined, done: true } generator.next(); { value: undefined, done: true } @gurlcode

Slide 54

Slide 54

function* forever(num) { while (true) { yield 2 + num; } } var generator = forever(2); Generator {}; generator.next(); { value: 4, done: false } generator.return(); { value: undefined, done: true } @gurlcode

Slide 55

Slide 55

function* forever(num) { while (true) { yield 2 + num; } } var generator = forever(2); Generator {}; generator.next(); { value: 4, done: false } generator.return(); { value: undefined, done: true } @gurlcode

Slide 56

Slide 56

Resuming the generator occurs outside of the generator. function* generatorFunc() { yield 1; yield 2; } var generator = generatorFunc(); generator.next(); @gurlcode

Slide 57

Slide 57

s n u r s r o t a r e n e g @gurlcode

Slide 58

Slide 58

Coroutines! @gurlcode

Slide 59

Slide 59

Coroutines are a general control structure whereby control flow is cooperatively passed between two different routines. @gurlcode

Slide 60

Slide 60

function coroutine(generatorFunc) { const generator = generatorFunc(); nextResponse(); function nextResponse(value) { const response = generator.next(value); if (response.done) { return; } nextResponse(response.value); } } @gurlcode

Slide 61

Slide 61

function coroutine(generatorFunc) { const generator = generatorFunc(); nextResponse(); function nextResponse(value) { const response = generator.next(value); if (response.done) { return; } nextResponse(response.value); } } @gurlcode

Slide 62

Slide 62

function coroutine(generatorFunc) { const generator = generatorFunc(); nextResponse(); function nextResponse(value) { const response = generator.next(value); if (response.done) { return; } nextResponse(response.value); } } @gurlcode

Slide 63

Slide 63

function* bounceUp() { yield context.drawImage(…); yield context.drawImage(…); yield context.drawImage(…); // …etc } coroutine(bounceUp); @gurlcode

Slide 64

Slide 64

function* bounceUp() { yield new Promise(…); yield new Promise(…); yield new Promise(…); // …etc } coroutine(bounceUp); @gurlcode

Slide 65

Slide 65

function nextResponse(value) { const response = generator.next(value); if (response.done) { return; } handleAsync(response.value); } @gurlcode

Slide 66

Slide 66

function handleAsync(value) { value.then(nextResponse); } @gurlcode

Slide 67

Slide 67

function bounce() { return bounceUp .then(bounceDown); }

coroutine(function* bounce() { yield bounceUp; yield bounceDown; }); @gurlcode

Slide 68

Slide 68

co(function* () { yield promise(); yield (callback) => { // do some things callback() }; yield generatorFunction; }); https://github.com/tj/co @gurlcode

Slide 69

Slide 69

You can think sequentially about async code. function* idle() { yield* bounce; yield* moveRight; yield* moveLeft; yield* bounce; } @gurlcode

Slide 70

Slide 70

@gurlcode

Slide 71

Slide 71

function delay(ms) { return new Promise((resolve) => setTimeout(() => resolve(), ms)); } function clear() { context.clear(…); } function draw(image, frame, …) { context.drawImage(image, frame, …); } @gurlcode

Slide 72

Slide 72

function delay(ms) { return new Promise((resolve) => setTimeout(() => resolve(), ms)); } function clear() { context.clear(…); } function draw(image, frame, …) { context.drawImage(image, frame, …); } @gurlcode

Slide 73

Slide 73

function delay(ms) { return new Promise((resolve) => setTimeout(() => resolve(), ms)); } function clear() { context.clear(…); } function draw(image, frame, …) { context.drawImage(image, frame, …); } @gurlcode

Slide 74

Slide 74

function* drawFrame(image, frame, ms = 100) { clear(); draw(image, frame); yield delay(ms); } function* dislike() { yield* drawFrame(‘dislike’, 0); yield* drawFrame(‘dislike’, 1); } @gurlcode

Slide 75

Slide 75

function* drawFrame(image, frame, ms = 100) { clear(); draw(image, frame); yield delay(ms); } function* dislike() { yield* drawFrame(‘dislike’, 0); yield* drawFrame(‘dislike’, 1); } @gurlcode

Slide 76

Slide 76

@gurlcode

Slide 77

Slide 77

coroutine(function* loop() { let done; let animation = idle(); while (true) { if (pending) { animation.return(); yield* event(); } while (!done && !pending) { const next = animation.next(); done = next.done; yield next.value; } animation = idle(); done = false; } }); @gurlcode

Slide 78

Slide 78

coroutine(function* loop() { let done; let animation = idle(); while (true) { if (pending) { animation.return(); yield* event(); } while (!done && !pending) { const next = animation.next(); done = next.done; yield next.value; } animation = idle(); done = false; } }); @gurlcode

Slide 79

Slide 79

coroutine(function* loop() { let done; let animation = idle(); while (true) { if (pending) { animation.return(); yield* event(); } while (!done && !pending) { const next = animation.next(); done = next.done; yield next.value; } animation = idle(); done = false; } }); @gurlcode

Slide 80

Slide 80

coroutine(function* loop() { let done; let animation = idle(); while (true) { if (pending) { animation.return(); yield* event(); } while (!done && !pending) { const next = animation.next(); done = next.done; yield next.value; } animation = idle(); done = false; } }); @gurlcode

Slide 81

Slide 81

function* feed() { if (hunger === 0) { yield* dislike(); } } function* dislike() { yield* drawFrame(‘dislike’, 0); yield* drawFrame(‘dislike’, 1); } hunger—-; yield* eat(); @gurlcode

Slide 82

Slide 82

function* feed() { if (hunger === 0) { yield* dislike(); } } hunger—-; yield* eat(); function* eat() { yield* drawFrame(‘eat’, 0); yield* drawFrame(‘eat’, 1); } @gurlcode

Slide 83

Slide 83

@gurlcode

Slide 84

Slide 84

Thank you! https://github.com/jcreighton/tamagotchi @gurlcode