Basic generators

Let's start with a simple generator function. The difference between a generator function and a normal function declaration is the * between function and the function name.

function *sample() {
  yield 1;
  yield 2;
  yield 3;
}

We can either use function* sample() or function *sample() or function * sample(). It's up to the development team to choose the desired format. In this book, function *sample() is used as it's the default configuration in ESLint.

Generator objects can return multiple values when next() method is invoked. Those values are specified using yield keyword. In the generator function above, three yield expressions can generate three values 1, 2 and 3 when next() method of a generator object is invoked.

let func = sample();
func.next();
// -> {value: 1, done: false}
func.next();
// -> {value: 2, done: false}
func.next();
// -> {value: 3, done: false}
func.next();
// -> {value: undefined, done: true}
func.next();
// -> {value: undefined, done: true}

In the code above, invoking the generator function sample generates a new generator object func. Execution of generator object func is initially suspended. When next method is invoked on the func object, it starts execution and runs to the first yield expression and returns the value 1 to the caller. The return value is an object with two properties: value and done. value contains the return value of yield expression, done can be used to check if there are more values to get. done property is false for the first three invocations of next method. For the fourth invocation, done property is set to true, which means there are no values anymore.

Suspend & resume execution

The power of generators comes from the ability to suspend and resume execution of generator objects. Each generator object can be viewed as a state machine. Each instance of the same generator function maintains its own state. Invoking next() on the generator object triggers state transition inside the object, which causes the object runs to the next yield expression. This continues until no more yield expressions found.

In the code below, two generator objects func1 and func2 maintain their own internal states. Invoking next() on one object doesn't affect the state of the other object.

let func1 = sample();
let func2 = sample();
func1.next();
// -> {value: 1, done: false}
func2.next();
// -> {value: 1, done: false}
func1.next();
// -> {value: 2, done: false}

Check types of generator functions and generator objects

We can use Object.prototype.toString to check the types of generator functions and generator objects.

function *sample() {

}

Object.prototype.toString.apply(sample);
// -> "[object GeneratorFunction]"

Object.prototype.toString.apply(sample());
// -> "[object Generator]"

results matching ""

    No results matching ""