Mikk Kirstein / @kolmas

 

ES 6 generators*

Generators are functions which can be exited and later re-entered.

 

Their context (variable bindings) will be saved across re-entrances.

functions that can be paused ...

function* myGen() {
  console.log('margus');
  var a = yield 2;
  console.log('mahler');
  var b = yield 5;
  return a + b;
}
function* myGen() {
  console.log('margus');
  var a = yield 2;
  console.log('mahler');
  var b = yield 5;
  return a + b;
}

var it = myGen();
log(it);
// myGen {}

Iterator object

it.next(/* val */); // { value: ?, done: true|false };
it.throw(err); // { value: ?, done: true|false };
function* myGen() {
  console.log('margus');
  var a = yield 2;
  console.log('mahler');
  var b = yield 5;
  return a + b;
}

var it = myGen();
log(it);
// myGen {}
log(it.next());
// margus
// Object {value: 2, done: false}

The yield keyword is used to pause and resume a generator function.

A communication point between the iterator and the generator

function* myGen() {
  console.log('margus');
  var a = yield 2;
  console.log('mahler');
  var b = yield 5;
  return a + b;
}

var it = myGen();
log(it);
// myGen {}
log(it.next());
// margus
// Object {value: 2, done: false}
log(it.next());
// mahler
// Object {value: 5, done: false}
function* myGen() {
  console.log('margus');
  var a = yield 2;
  console.log('mahler');
  var b = yield 5;
  return a + b;
}

var it = myGen();
log(it);
// myGen {}
log(it.next());
// margus
// Object {value: 2, done: false}
log(it.next());
// mahler
// Object {value: 5, done: false}
log(it.next());
// ?
function* myGen() {
  console.log('margus');
  var a = yield 2;
  console.log('mahler');
  var b = yield 5;
  return a + b;
}

var it = myGen();
log(it);
// myGen {}
log(it.next());
// margus
// Object {value: 2, done: false}
log(it.next());
// mahler
// Object {value: 5, done: false}
log(it.next());
// Object {value: NaN, done: false}
function* myGen() {
  console.log('margus');
  var a = yield 2;
  console.log('mahler');
  var b = yield 5;
  return a + b;
}

var it = myGen();
log(it);
// myGen {}
log(it.next());
// margus
// Object {value: 2, done: false}
log(it.next());
// mahler
// Object {value: 5, done: false}
log(it.next());
// Object {value: NaN, done: false}
log(it.next());
// Object {value: undefined, done: true}
function* fib() {
  var a = 0;
  var b = 1;
  var c = 0;
  while (true) {
    c = a;
    a = b;
    b += c;
    yield a;
  }
}
function* fib() {
  var a = 0;
  var b = 1;
  var c = 0;
  while (true) {
    c = a;
    a = b;
    b += c;
    yield a;
  }
}

var it = fib();
function* fib() {
  var a = 0;
  var b = 1;
  var c = 0;
  while (true) {
    c = a;
    a = b;
    b += c;
    yield a;
  }
}

var it = fib();
log(it.next().value); // 1
function* fib() {
  var a = 0;
  var b = 1;
  var c = 0;
  while (true) {
    c = a;
    a = b;
    b += c;
    yield a;
  }
}

var it = fib();
log(it.next().value); // 1
log(it.next().value); // 1
function* fib() {
  var a = 0;
  var b = 1;
  var c = 0;
  while (true) {
    c = a;
    a = b;
    b += c;
    yield a;
  }
}

var it = fib();
log(it.next().value); // 1
log(it.next().value); // 1
log(it.next().value); // 2
function* fib() {
  var a = 0;
  var b = 1;
  var c = 0;
  while (true) {
    c = a;
    a = b;
    b += c;
    yield a;
  }
}

var it = fib();
log(it.next().value); // 1
log(it.next().value); // 1
log(it.next().value); // 2
log(it.next().value); // 3
function* pow() {
  return Math.pow(yield 5, yield 2);
}
function* pow() {
  return Math.pow(yield 5, yield 2);
}

var it = pow();
function* pow() {
  return Math.pow(yield 5, yield 2);
}

var it = pow();
log(it.next().value); 
// 5
function* pow() {
  return Math.pow(yield 5, yield 2);
}

var it = pow();
log(it.next().value); 
// 5
log(it.next(2).value); 
// 2
function* pow() {
  return Math.pow(yield 5, yield 2);
}

var it = pow();
log(it.next().value); 
// 5
log(it.next(2).value);
// 2
log(it.next(3).value);
function* pow() {
  return Math.pow(yield 5, yield 2);
}

var it = pow();
log(it.next().value); 
// 5
log(it.next(2).value);
// 2
log(it.next(3).value);
// 8
function* pow() {
  return Math.pow(yield 5, yield 2);
}

var it = pow();
log(it.next().value); 
// 5
log(it.next(2).value);
// 2
log(it.next(3).value);
// 8
log(it.next());
// { done: true, value: undefined }

Iterating a generator

function* count() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 8;
}

for (var v of count()) {
    console.log(v);
}
// 1 
// 2 
// 3 
// 4 
// 5

Control Flow!

... back to basics

JavaScript is a language which is

  • Dynamic 
  • Single Threaded
  • Asynchronous
  • Concurrency based on event loop
  • Functions as first class citizens

Callbacks

Callbacks suck because...

they are hard to follow

var usr;
$.get("user", function(user) {
    usr = user;
});

console.log(usr);

Callbacks suck because...

its hard to handle errors

// This. Wont. Work.
try {
    $.get("user", function(user) {
        $.get("posts/" + user.id, function(posts) {
            throw new Error('Oops?!');
            // Magic...
        });
    });
} catch (ignored) {

}




$.get("user", function(err, user) {
 


    $.get("posts/" + user.id, function(err, posts) {
        


        // Magic...
    });
});
function onError(err) {
    // TODO: Dammit, Margus, handle it!
}

$.get("user", function(err, user) {
    if (err) {
        return onError(err);
    }
    $.get("posts/" + user.id, function(err, posts) {
        if (err) {
            return onError(err);
        }
        // Magic...
    });
});

Callbacks suck because...

they are HARD to follow

var data = {};

function login(data) {
  $.post('login', data, function(user) {
    console.log(user);
  });
}

$.get('token', function(token) {
  data.token = token;
  if (data.token && data.username) {
    login(data);
  }
});

$.get('username', function(username) {
  data.username = username;
  if (data.token && data.username) {
    login(data); 
  }
});
var data = {};

function login(data) {
  $.post('login', data, function(user) {
    console.log(user);
  });
}

$.get('token', function(token) {
  data.token = token;
  if (data.token && data.username) {
    login(data);
  }
});

$.get('username', function(username) {
  data.username = username;
  if (data.token && data.username) {
    login(data); 
  }
});
async(function * (resume) {
  var data      = {}
  data.token    = yield $.get('token', resume);
  data.username = yield $.get('username', resume);
  var user      = yield $.post('login', data, resume);
  console.log(user);
});
async(function * (resume) {
  try {
    var data      = {}
    data.token    = yield $.get('token', resume);
    data.username = yield $.get('username', resume);
    var user      = yield $.post('login', data, resume);
    console.log(user);
  } catch (e) {
    // OMFG THIS WORKS!?!
    console.log(e);
  }
});

Putting it all together

as a coroutine

function async(gen) {
  var it;
  
  function resume(err, ret) {
    if (err) {
      return it.throw(err);
    } 
    it.next(ret);
  }  
  
  it = gen(resume);
  it.next();
}
async(function * (resume) {
  try {
    var data      = {}
    data.token    = yield $.get('token', resume);
    data.username = yield $.get('username', resume);
    var user      = yield $.post('login', data, resume);
    console.log(user);
  } catch (e) {
    // OMFG THIS WORKS!?!
    console.log(e);
  }
});

Libraries

Available on

  • node 0.11+ with harmony
  • chrome canary
  • firefox nightly
  • firefox developer edition
  • traceur

ES 6 generators*

By Mikk Kirštein

ES 6 generators*

  • 1,236