Generator Back

Generator is a concept specified in ECMAScript 2015 (ES6), which stands for state machines like objects, which have an iterable protocol. It means that we can easily use the Generator.prototype.next() method to access the next state of such a machine.

In JavaScript, if you want to create a generator, you should use function* rather than Generator, as it is not a constructor.

function* gen() {
    yield 1;
    yield 2;
}

/**
 * a generator
 * @type {IterableIterator<number>}
 */
const generator = gen();

With such an object, there are three exposed methods we can use:

  1. Generator.prototype.next(): returns a value yielded by the yield expression.

     const generator = gen();
     generator.next(); // => {value: 1, done: false}
     generator.next(); // => {value: 2, done: false}
     generator.next(); // => {value: undefined, done: true}
    
  2. Generator.prototype.return(): returns the given value and finishes the generator.

     const generator = gen();
     generator.next(); // => {value: 1, done: false}
     generator.return(); // => {value: undefined, done: true}
     generator.next(); // => {value: undefined, done: true}
    
  3. Generator.prototype.throw(): throws an error to the generator, and finishes it, unless the error caught by the generator.

     const generator = gen();
     generator.next(); // => {value: 1, done: false}
     generator.throw(1); // => Uncaught 1
     generator.next(); // => {value: undefined, done: true}
    

So how can we use generators to describe a state machine? Take the following case as an example. Assume that there is a state machine like this:

generator
Figure 1 a simple state machine

And then the generator for this can code like this:

function* machine() {
    // the start of state
    let state = 0;
    let action;

    // simple prev checker
    const _next = (prev, val) => state === prev ? val : state;

    for (;;) {
        /**
         * get the initial state, and read "action"
         * passed by `Generator.prototype.next()` each time
         */
        action = yield state;
        state = _next.apply(0, ({'E': [0, 1], ';': [1, 2], 'L': [2, 3], 'ε': [0, 3]})[action]);

        // the end of state
        if (state === 3) return state;
    }
}
const generator = machine();
generator.next(); // need to call next at first for initializing states

generator.next('E'); // => {value: 1, done: false}
generator.next(';'); // => {value: 2, done: false}
generator.next('L'); // => {value: 3, done: true}
const generator = machine();
generator.next(); // need to call next at first for initializing states

generator.next('ε'); // => {value: 3, done: false}

Quite easy, right? I have wrapped it as a simple library: @aleen42/state. In addition, we will take a look at some usages of yield:

  1. If we want to combine two generators, or embed one into another, we can use yield *:

     function* top(i) {
         yield i + 1;
         yield i + 2;
         yield i + 3;
     }
    
     function* decade(i) {
         yield i;
         yield * top(i);
         yield i + 10;
     }
    
     const generator = decade(10);
     generator.next(); // => {value: 10, done: false}
     generator.next(); // => {value: 11, done: false}
     generator.next(); // => {value: 12, done: false}
     generator.next(); // => {value: 13, done: false}
     generator.next(); // => {value: 20, done: false}
    
  2. Pass arguments into generators:

     function* log() {
         console.log(0);    
         console.log(1, yield);    
     }
    
     const generator = log();
     generator.next('first'); // => 0
     generator.next('second'); // => 1 second
    

    As we can see in the snippet above, that's why we need to initialize states at first with calling Generator.prototype.next().

  3. Return statements in a generator:

     function* method() {
         yield 'a';
         return 'close';
         yield 'unreachable'; // the generator will be marked as done once it returns
     }
    
     const generator = method();
     generator.next(); // => {value: "a", done: false}
     generator.next(); // => {value: "close", done: true}
     generator.next(); // => {value: undefined, done: true}
    
  4. Generator as an object property:

     const obj = {
         * gen() {
             yield 1;
             yield 2;
         },
     };
    
     const generator = obj.gen();
    
  5. Generator as an class member:

     class Component {
         constructor() {}
    
         * gen() {
             yield 1;
             yield 2;
         }
     }
    
     const component = new Component();
     const generator = component.gen();
    
Empty Comments
Sign in GitHub

As the plugin is integrated with a code management system like GitLab or GitHub, you may have to auth with your account before leaving comments around this article.

Notice: This plugin has used Cookie to store your token with an expiration.