Generator function is a function that can pause, accept some input and run again.

Invoking a generator function

Consider the printHello function which returns “Hello world”

function* printHello() {
  yield 'Hello world';
}

When a function has an asterix (*) as suffix, it becomes a generator function. How do we invoke a generator function?

const iterator = printHello();
let result = iterator.next();
console.log(result.value, result.done);
result = iterator.next();
console.log(result.value, result.done);

Calling a generator function produces an iterator. The iterator has a next method. Calling the next method produces a result. This result has a value and a boolean flag which indicates if the function has completed execution.

In our example, calling next() for the first time gives us the resultant value of Hello world. Technically, the function has not completed execution. So, done is false. Calling next()for the second time indicates that the function has completed execution.

We will consider three variants of the above example:
1. Ending the iterator using return statement
2. Passing an argument to the generator function
3. Passing an argument to the next method

Ending the iterator using return statement

Consider the variation of printHello function which returns some value.

function* printHello() {
  yield 'Hello world';
  return 'End of hello';
}

const iterator = printHello();
let result = iterator.next();
console.log(result.value, result.done);
result = iterator.next();
console.log(result.value, result.done);

When we pass a value from the return statement, this reflects in the resultant value of the second next method call. Note that the done prop of the result has a value set to true. Which indicates that the function has completed execution. If there is no return statement, the result has a value of undefined, after the last next method call.

Passing argument to generator function

Consider the case where we want to customise the “Hello” string with an argument to the printHello function.

function* printHello(helloString) {
  yield helloString + ' world';
}

const iterator = printHello('halo');
let result = iterator.next();
console.log(result.value, result.done);
result = iterator.next();
console.log(result.value, result.done);

We call printHello with the text ‘halo’ instead of the regular ‘hello’.

Passing an argument to the next method

We use the yield keyword to return an intermediate value. The same keyword can accept some an input argument as well. Consider the following code snippet.

function* printHello(helloString) {
  const person = yield;
  yield helloString + ' ' + person;
}

const iterator = printHello('halo');
let result = iterator.next();
console.log(result.value, result.done);
result = iterator.next('vijay');
console.log(result.value, result.done);
result = iterator.next();
console.log(result.value, result.done);

The first next() method runs the generator function till it encounters the first yield statement. This yield statement does not return any value. But rather it accepts an input argument. We pass this input argument to the second next()method call. The second yield statement returns a value. The console has the following output.

Summary

Generator functions are defined using the function* keyword (Note the *). Calling the function produces an iterator: an object with a next method. Calling the next method produces a result. The result is a value returned by a yield keyword. Sometimes, we pass an argument to the next method. This will be the value of the yield operator which can be assigned to a variable. In this way, yield operator returns a value or accepts a value (two way messaging to a generator function). We learnt how the next method and yield operator work together.

There is a CodePen if you want to play with it.

Related Posts

Leave a Reply

Your email address will not be published.