Generators

SydJS March 2015




Patrick Roumanoff • Atlassian • @pkr2

History

1963: Melvin Conway wrote a paper about coroutines

appeared in simula, modula2, python, lua, C (with macro)
Firefox (js1.7), ES6, v8, chrome, safari


This is not new stuff!

A Bit of Theory

  • Routine is main
  • subRoutine is a function: 1 entry, 1 or more exit
  • coRoutine is a generalisation of subRoutine
    more than 1 entry point
  • Continuation, Fiber, Thread...

Iterator

  • An iterator is something that goes in a for-in loop
  • _.each of underscore.js
  • Array.prototype.forEach
  • var iterator = ...;
    for(let i of iterator) {
      // do smart stuff with i
    }
  • Generators are a simple way to build iterators


What's so special about it that we need a new version of JS?

Generator

  • Special function * syntax to define a generator
  • generator function behaves like a constructor
  • yield keyword to expose a return point and an entry point
  • yield behaves like return with a twist
function *generator () {
  yield 1; 
  return 0;
};
var a = generator();
console.log(a.next()); //{value: 1; done: false}
console.log(a.next()); //{value: 0; done: true}
console.log(a.next()); //{done: true}

Generator

  • yield allow to send value in as well
  • you can throw exception at a generator
  • generators can throw exception at you as well
function *generator (init) {
  var a = yield init;
  yield a;
  throw "go away!";
};
var a = generator("hello");
console.log(a.next()); //{value: "hello"; done: false}
console.log(a.next("SydJS")); //{value: "SydJS"; done: true}
console.log(a.throw("u go away")); 

states

Enough with the theory

  • Iterator
  • Callback solving
  • Promise
  • Async/await

Iterator

Infinite sequences!
function *square() {
  var i = 1;
  while(i++)
    yield i*i;
}

for(let sq of square()) {
  if(sq  > 10000) break;
  console.log(sq);
}

Iterator

function Tree(left, label, right) {
  this.left = left;
  this.label = label;
  this.right = right;
}
function* inorder(t) {
  if (t) {
    yield* inorder(t.left);
    yield t.label;
    yield* inorder(t.right);
  }
}

for (let node of inorder(tree)) {
  //...
}

Callback Hell

a.readFile(function (aValue) {
  b.post(aValue, function (bValue) {
    c.commit(function (cValue) {
      keepGoing(aValue, bValue, cValue);
    }
  }
}

aka pyramid of doom

The promise Solution

a.readFile()
  .then(b.post)
  .then(c.commit)
  .then(keepGoing)

but it requires your callback to cooperate

Is the future still a promise?

with the Q.js library

Q.async(function*() {
  var aValue = yield a.readFile();
  var bValue = yield b.post(aValue);
  var cValue = yield c.commit(bValue);
  keepGoing(aValue, bValue, cValue);
})().done();

Async/Await

suspend.js(86), genrun(24)

suspend(function*(resume) {
  var aValue = yield a.readFile(resume);
  var bValue = yield b.post(aValue, resume);
  var cValue = yield c.commit(bValue, resume);
  keepGoing(aValue, bValue, cValue);
})();

Async/Await

suspend.js(86), genrun(24)

suspend(function*(resume) {
  try {
    var aValue = yield a.readFile(resume);
    var bValue = yield b.post(aValue, resume);
    var cValue = yield c.commit(bValue, resume);
    keepGoing(aValue, bValue, cValue);
  } catch (e) {
    // ...
  }
})();

Yesterday: Firefox

since FF2 - 2006

Today: Chrome

Today: Node

Today: Everyone

Traceur, the google backward compiler but also Facebook's regenerator, babel, etc.

Want more?

Thank you!





Patrick Roumanoff • Atlassian • @pkr2