JavaScript Generator Function (function*)

The interview question I could not answer maximum number of times.

What is the Generator function in JavaScript?

The embarrassing thing is, I have read about it. Still was not able to answer in my last interview.

I just thought that I should get to the bottom of this.

Checking MDN library for this function... The definition is:

"The function* declaration creates a binding of a new generator function to a given name. A generator function can be exited and later re-entered, with its context (variable bindings) saved across re-entrance"

All I understand from the definition is, it can be called later and its context will be saved for reuse. Meh! let's look at examples.

Example from MDN:

function* generator(i) { yield i; yield i + 10; }
const gen = generator(10);
console.log(gen.next().value); // Expected output: 10
console.log(gen.next().value); // Expected output: 20

There are a lot of unknowns to me here. Namely:

  1. "function*" keyword: generator function declaration (kinda obvious).

  2. "yield" keyword.

  3. .next().value: somewhat self-explanatory. It gets the next value of the generator variable.

So what is yield?

"The yield operator is used to pause and resume a generator function."

Ahh! OK. so it is an operator.

So, the "yield" operator does its work whenever we call the generator function, I guess.

Ah! Some simple explanation is given in the "Description" section on the "Yield" MDN page.

"The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator's caller."

So, on calling the generator function, the execution happens inside the function. On reaching the yield operator, the following calculation is executed.

From our example: "{ yield i; yield i + 10; }"

Then we get this expression executed whenever we call .next() function.

That was easy. He he he. :)

Hmm, that felt a little easy... so far.

The caveat

Let's look at our example once again. What if we call gen.next().value a third time?

function* generator(i) {
  yield i;
  yield i + 10;
}
const gen = generator(10);
console.log(gen.next().value); // Expected output: 10
console.log(gen.next().value); // Expected output: 20
console.log(gen.next().value); // = undefined ( I did not expect this in the begining)

If you were not as dumb as me, you would already have seen that and would have predicted the correct outcome.

So, to let the yield operator resume and pause the third time we need to put a third expression inside the generator function. Something like this:

function* generator(i) {
  yield i;
  yield i + 10;
  yield i + 10; // <- this is needed for the third next()
}
const gen = generator(10);
console.log(gen.next().value); // Expected output: 10
console.log(gen.next().value); // Expected output: 20
console.log(gen.next().value); // output: 30

So, I can put an infinite loop inside the generator function and call the next() function any number of times. Like this:

function* generator(i) {
  while (true) {
    yield i = i + 10; // Mind the logic here. The previous value must be incremented
  }
}
const gen = generator(10);
console.log(gen.next().value); // output: 10
console.log(gen.next().value); // output: 20
console.log(gen.next().value); // output: 30 and will go on incrementing

Phew! finally figured it out.

Instead of using a pre-defined array of uncertain length, I could use a generator function to instantly (asynchronously) resume the generation of new values.

Or, I can use a predefined array(maybe even Set or Map) of values to pick the next value. Example:

function* countAppleSales() {
  const saleList = [3, 7, 5];
  for (let i = 0; i < saleList.length; i++) {
    yield saleList[i];
  }
}

Nice!

I am getting ideas like, since I can pick the next value from an array, maybe a list also. Could I parse trees or linked lists? maybe. Maybe wishful thinking :)

But a practical thought came to me:

Why did the creators or maintainers of JavaScript create this generator function?

One answer I found was from the "JavaScript.info" website:

"Generators were added to JavaScript language with iterators in mind, to implement them easily."

From "geeksforgeeks.com":

"The values are generated but not stored in memory so it takes less time to execute."

The answer I found by asking ChatGPT was more specific. Check for yourself:

"The creators and maintainers of JavaScript recognized that traditional asynchronous patterns like callbacks and Promises had their limitations*, especially when it came to handling tasks like **iteration, lazy loading, and pausing and resuming asynchronous operations**. Generator functions were introduced to provide a solution to these issues.*"

There is more to the generator function

One has to look more closely to pick up the finer details. Then practice more to use these details in our day-to-day problems.

There are ways to end the execution of the generator function. We can implement the throw() function to show errors to be able to gracefully exit. More interesting examples to explore, using objects, expressions, and computed properties.

Plan to go beyond the interview answer in days to come.