generator

Iterator, No promise, no callback

Created by Howard.Zuo / @leftstick

What is generator

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.

From wikipedia

How it look?

The function keyword followed by an asterisk defines a generator function, which returns a Generator object:

Syntax


function* name([param[, param[, ... param]]]) {
   statements
}
						

How to use a generator?

Let's try with simple snippet:


'use strict'

var idMaker = function* (){
    var index = 0;
    while(index < 3){
        yield index++;
    }
};

var gen = idMaker();

console.log(gen.next()); // value: 0, done: false
console.log(gen.next()); // value: 1, done: false
console.log(gen.next()); // value: 2, done: false
console.log(gen.next()); // value: undefined, done: true
					

Difference from normal function

Run To Completion

Once the function starts running, it will always run to completion before any other JS code can run

'use strict'

setTimeout(function(){
    console.log('Hello World');
}, 1);

var occupy = function() {
    //You should never writing as following, since it's blocking the main thread
    for (var i=0; i<=100; i++) {
        console.log(i);
    }
};

occupy();
// 0...100, take a long time than 1 millisecond
// 'Hello World' is printed right after occupy run to completion.
//The setTimeout cannot interrupt occupy() function while it's running
						

Run Stop Run

Generator is such a different function, which may be paused in the middle, one or multiple times, and resumed later, allowing other code to run during these paused periods.

Generator can:

  • only be paused inside itself
  • only be resumed by external control
  • process infinite loop
  • be interpolated while running

Decomposition


'use strict'

var hello = function* () {
    //step 3. The yield "world" expression will send the "world" string
    //value out when pausing the generator function at that point
    var str = (yield 'world');

    return 'Hello ' + str;//step 5. final value returned
};

//step 1. get generator object, which conforms to both the iterator
//and the Iterable protocol
var gen = hello();

//step 2. get the value "world" from paused point where yield placed
console.log(gen.next());

//step 4. re-enter the generator with capitalized "World" passed in
//as the result of (yield 'world') expression
var finalResult = gen.next('World');
console.log(finalResult);//{ value: 'Hello World', done: true }
						

APIs of Generator Object

Name Description
next() Returns a value yielded by the yield expression.
doesn't support in node
return()?
Returns the given value and finishes the generator.
throw() Throws an error to a generator.

next

Syntax


gen.next(value)
						

Parameters

The value to send to the generator.

Example


'use strict'
var gen = function*() {
    while (true) {
        var value = yield null;
        console.log(value);
    }
};
var g = gen();
console.log(g.next(1));//won't log anything in generator, since nothing yielding initially
console.log(g.next(2));//generator will log "2"
						

throw

Syntax


gen.throw(exception)
						

exception

The exception to throw. For debugging purposes, it is useful to make it an instanceof Error.

Example


'use strict'
var gen = function*() {
    try {
        yield 1;
    } catch (e) {
        console.log('ERROR: ', e);//step 3. get error thrown in step 2
    }
};
var g = gen();
g.next(); //step 1. { value: 1, done: false }
g.throw(new Error('What the fuck!')); //step 2.
						

yield

Syntax


[rv] = yield [expression];
						

expression

Defines the value to return from the generator function via the iterator protocol. If omitted, undefined is returned instead.

rv

Returns the optional value passed to the generator's next() method to resume its execution.

What is generator for

Iterator


'use strict'

var fibonacci = function*() {
    var fn1 = 1, fn2 = 1;
    while (true) {
        var current = fn2;
        fn2 = fn1;
        fn1 = fn1 + current;
        yield current;
    }
};

var n;

for (n of fibonacci()) {
    console.log(n);
    // truncate the sequence at 1000
    if (n >= 1000) {
        break;
    }
}
						

Control Flow

We've known few ways of handling async task:

  • callback
  • publish/subscribe
  • promise(syntactic sugar of callback)

What's new in ES2015

Generator is sort of coroutine implementation in ES2015

With yield's pausing ability

, let's try controlling an async task with generator and co(see below)

'use strict';

var co = require('co');

var getSession = function() {
    return Promise.resolve({userId: '1234'});
};

var getUserById = function(userId) {
    return userId ? Promise.resolve({
        userId: userId,
        name: 'hello'
    }) : Promise.reject(new Error('no such user'));
};

var getCurrentUser = function*() {
    try {
        var session = yield getSession();//step 2.
        var user = yield getUserById(session.userId);//step 4.
        console.log('user is ', user);
    } catch (e) {
        console.error('ERROR:', e);
    }
    return user;
};

co(getCurrentUser);//execute the generator automatically
						

How does co work?


'use strict';

var getSession = function() {
    return Promise.resolve({userId: '1234'});
};

var getUserById = function(userId) {
    return userId ? Promise.resolve({
        userId: userId,
        name: 'hello'
    }) : Promise.reject(new Error('no such user'));
};

var getCurrentUser = function*() {
    try {
        var session = yield getSession();//step 2.
        var user = yield getUserById(session.userId);//step 4.
        console.log('user is ', user);
    } catch (e) {
        console.error('ERROR:', e);
    }
    return user;
};

var it = getCurrentUser();
//step 1.the first yield push a promise out here
var sessionPromise = it.next().value;
sessionPromise
.then(function(session) {
    //once we get the result, pass it back as final result of session in generator
    //step 3.and since the second yield push a promise as well, we just return
    //it here to the next promise flow
    return it.next(session).value;
})
.then(function(user) {
    //finall we get user info, and pass it back to the generator
    //to make it printable inside generator
    it.next(user);
})
.catch(it.throw.bind(it));//trigger an error to be caught inside the generator
//while error occurs in this promise flow
						

THE END

- co
- Iterators and generators
- function*