Javascript: Generator functions

What is a generator function?

Generator functions are a new class of functions that were introduced by the EcmaScript 2015 standard. A generator is a function that can be paused mid-way at any point during the execution and resumed whenever required. The best part about the generator functions is that they preserve the execution context until they reach the end of the function. The variable values within the generators remain intact until the function execution is concluded.

How does a generator function work?

A normal function in javascript cannot be paused halfway through the execution. The control enters a function, executes it till the end and then the control goes out of the function.

function normalFunction() {
console.log("I am executing");
var data = 3;
console.log("I am still executing", data);
data = 4;
console.log("I am done executing", data);
}
  • The control goes out of the function only after it is fully executed.
  • The function execution starts from the very first line, regardless of where it is called from.

Generator function:

Generator functions behave in an unorthodox way. These functions can be exited mid-way through, and the function execution can be resumed from a different line of code, not necessarily from the beginning. A generator function has the following characteristics:

  • Generator function can be exited before it fully completes execution.
  • Generator function execution can be resumed from any line, not necessarily the first line.
  • Generator function returns an iterator object. The object can be used to execute the function call multiple times.
  • Generator function returns a sequence of values instead of a single value

How to create and use a generator function

The syntax for the creation of a generator function is to use “*” (an asterisk) before the function name. The generator function also comes with two new keywords that are not commonly used:

  1. yield: The place where yield is used is the place where the function pauses the execution. This keyword can also be used to return any data.
  2. next: The next acts much like a callback function. When it is called, the generator function resumes its execution.
function * generatorFunc() {
var value = "value";
console.log("I am starting my execution ", value);
yield "value1";

console.log("I am resuming execution after first pause ", value);
yield "value2";

console.log("I am resuming execution after second pause ", value);
}

const generator = generatorFunction();
console.log(generator.next().value); // I am starting my execution value
// vaule1
console.log(generator.next().value); // I am resuming execution after first pause value
// value2
console.log(generator.next().value); // I am resuming execution after second pause value
// undefined
  1. The generator function is like any normal function, with the yield keyword where the function needs to be paused.
  2. The first call to the generator function doesn’t start the execution of the code inside the function, however, it instantiates the function returns a generator object.
  3. The subsequent calls to the generator function is made by calling the next() function on the generatorObject that was returned from the function.
  4. The value returned from the yield comes back to the main control.
  5. The variable values within the generator function is preserved throughout the execution.

Uses of generators

The implementation of the generator function seems a little unorthodox, however, generator functions have many use cases:

Using generators as iterators:

Since a generator function can be repeatedly called, these functions can be used as iterators. While normally iterators can be implemented using an object and looping through the object, generator functions can simplify this.

function * loopme() {
yield "One";
yield "Two";
yield "Three";
}

for(const val of loopme()){
console.log(val);
}

// One
// Two
// Three

Async functions:

The normal way of handling asynchronous functions is using promises and chaining then() to a promise.

function foo() {
return new Promise((resolve, reject) => {resolve(3)});
}

foo()
.then((val) => console.log(val))
.catch((err) => console.log(err));
function foo() {
return new Promise((resolve, reject) => {resolve(3)});
}

function *asyncFunc() {
var data = yield foo();
console.log(data);
}
var asyncFuncObj = asyncFunc();
var promiseObj = asyncFuncObj.next().value;

promsieObj
.then((val) => console.log(val))
.catch((err) => console.log(err));

Advantages of generator functions

Lazy evaluation:

The generator functions implement a lazy evaluation technique. This means the values inside the generator functions are only computed when they are needed. Let’s look at an example:

function * lazy() {
var x = 2;
var y = yield x;
return y + x;
}

var lazyObj = lazy();
lazyObj.next()
console.log('sum: ', lazyObj.next(3).value);