How ES6 Generators are changing the way we write Javascript

I have to admit, It took me some time to figure out in which use cases ES6 generators could be really useful (I’m not the only one I think, am I ?). When we talk about ES6 (or ES2015 as you like), generators are not the first feature we think about, we often see them as an easy way to create custom iterators, but in reality they are much more than that, they are maybe one of the most interesting features added in ES6.

What are generators and how do they work ?

If you're already familiar with how generators work, you can skip this section.

Generators are special functions that can be run, paused and resumed at different stages of their execution, thanks to the special keyword yield.

here is a simple example of an ES6 generator

function* myGenerator() {
  yield ‘first’;
  let input = yield ‘second’;
  yield input;
}
// Getting the generator object
let gen = myGenerator();
// Launching the generator
console.log(gen.next()): // { value: ‘first’, done: false }
// First resume (no passed value)
console.log(gen.next()); // { value: ‘second’, done: false }
// First resume (pass a value)
console.log(gen.next(‘third’)); // { value: ‘third’, done: false }
// Last (no more yield)
console.log(gen.next()); // { value: undefined, done: true }

So, what just happened here ?

  • We declared a generator function using the special syntax function* myfunction() {}
  • Calling this function at first returns a generator object. This object has a method next to resume the generator function at its current state.
  • The generator function does not start its execution until the first gen.next is triggered.
  • Each time gen.next is called, the generator function is resumed and run until the next yield. the gen.next call returns an object containing the yielded value and a flag telling if the generator function has ended or not.
  • You may also have noticed that we can pass data to the generator function.

Generators as iterators

I said at first that generators are often used as an easy way to create iterators. That’s because ES6 allows iterating over a generator object using the for of syntax like so :

function* myGenerator() {
  yield ‘first’;
  yield ‘second’;
  yield ‘third’;
}
for (var v of myGenerator()) {
  console.log(v);
}

Asynchronous control flow

Iterators are nice but what if we can use generators to make our asynchronous code way better.

Say we want to login to some backend, and then use the authentication token to fetch some data from this API. Our code using promises might look like this.

function login(username, password) {
  return fetch(‘/login’, {
    method: ‘post’,
    body: JSON.stringify({
      username: username
      password: password
    })
  }).then(response => response.json());
}
function getPosts(token) {
  return fetch(‘/posts’, {
    headers: new Headers({
      ‘X-Security-Token’: token
    })
  }).then(response => response.json());
}
const username = ‘’;
const password = ‘’;
login(username, password)
  .then(result => getPosts(result.token))
  .then(posts => {
    console.log(posts);
  })
  .catch(err => {
    console.log(err);
  });

But some talented folks out there (TJ Holowaychuk) thought we can do better and make this looks more synchronous using generators. Here is what we get:

// using the same login and getPosts functions as above
co(function* () {
  var result = yield login(username, password);
  var posts = yield getPosts(result.token);
  return posts;
}).then(value => {
  console.log(value);
}, err => {
  console.error(err);
});

In the above example I used the co library, which is a runtime around generators that make async calls look synchronous. For each yielded promise, It runs the promise and passes the result back to the generator function. Besides promises, the library can handle a lot more: generators, thunks, arrays, objects…

From express to Koa

In Node.JS, express has been for a long period the de facto framework for REST services. But the folks behind it came up with a new generation framework called Koa that make an intense usage of generators (replaced by async/await in the next version).

Take this idea of wrapping generators in a runtime and apply it to express/connect middlewares. So instead of calling the next callback, you yield the next generator. Here is a simple application example using KOA (taken from their website).

var koa = require(‘koa’);
var app = koa();
// logger 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);
});
// response middleware
app.use(function *(){
  this.body = yield db.find(‘something’);
});
app.listen(3000);

quite powerful don’t you think ?

What about frontend ?

The same trend is getting to frontend. If you use React, Redux and follow the ecosystem around them (or may be read my previous post), you may have heard about redux-saga.

Redux-Saga is a an alternative side effect model for Redux applications. It handles all the asynchronous control flow of Redux applications in a central place using … guess what 😛, generators.

It works in the same way as the co library, a runtime around generators but I would like to notice here this simple idea used in redux-saga to make testing your generators straightforward: Instead of triggering the async call in the generator function itself, yield a description of the async call to perform and let the runtime trigger the async call for you.

To make this gibberish a little bit clearer, let’s try to rewrite our previous example using this approach :

// using the same login and getPosts functions as above
// quick and dirty runtime implementation
// don’t take too much attention
const runtime = (generator, …args) => {
  return new Promise((resolve, reject) => {
 
    let gen = generator.apply(null, args);
    const next = (ret) => {
      const { value, done } = gen.next(ret);
 
      if (done) {
        return resolve(ret);
      }
 
      if (typeof value === ‘object’ && value.type === ‘call’) {
        const { context, callback, args } = value;
        return callback.apply(context, args)
          .then(res => next(res))
          .catch(err => reject(err));
      }
      // Handle other types of yieldable values 
    };
 
    next();
  });
}
const call = (context, callback, …args) => {
  return {
    type: ‘call’,
    context: context,
    callback: callback,
    args: args
  };
}
const myGenerator = function* (username, password) {
  var result = yield call(null, login, username, password);
  var posts = yield call(null, getPosts, result.token);
  return posts;
}
runtime(myGenerator, username, password).then(value => {
  console.log(value);
}, err => {
  console.error(err);
});

Don’t take too much attention to the runtime, It’s just a dirty implementation of a runtime similar to co but with handling effects (description of async calls).

So you see now my generator function doesn’t call itself the async functions, but let the runtime take care of this. So testing my business logic (my generators), is as easy as:

// some test data
const username = ‘myUsername’;
const password = ‘myPassword’;
const myLoginResult = { token : ‘myToken’ };
const myPosts = [{ title: ‘title’ }];
// the generator to test
const gen = myGenerator();
// Asserts
expect(gen.next().value).toEqual(call(null, login, username, password)); 
expect(gen.next(myLoginResult).value).toEqual(call(null, getPosts, myLoginResult.token)); 
expect(gen.next(myPosts).value).toEqual(myPosts);

Going further :


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Riad Benguella

Subscribe now to keep reading and get access to the full archive.

Continue reading