Working with generator functions in Javascript

What are generator functions?

Function generators are async functions, meaning every other code below them has to wait till they are done executing. They are a completely new type of function and are significantly different from standard, run-of-the-mill functions. A generator is a function that generates a sequence of values, but not all at once, as a standard function would, but on a per-request basis. We have to explicitly ask the generator for a new value, and the generator will either respond with a value or notify us that it has no more values to produce*.* What’s even more curious is that after a value is produced, a generator function doesn’t end its execution, as a normal function would. Instead, a generator is merely suspended. Then, when a request for another value comes along, the generator resumes where it left off.

How do we use generator functions?

Creating a generator function is simple: We append an asterisk (*) after the function keyword. This enables us to use the new yield keyword within the body of the generator to produce individual values.

function* appGenerator() { yield 'Netflix'; yield 'Google'; yield 'Facebook'; }

const appNames = appGenerator()

console.log(appNames.next()) // {value: 'Netflix', done: false}

the result, which is an object that has the key-value pair of value and done respectively is pretty much self-explanatory as it shows the current value we have yielded and the status of the generator, in this case, we are not yet done with all the values we initially defined to be yielded.

Yield & Next methods in generator functions

The yield keyword is used to assign values to the data that will be generated, it pauses the generator function execution while returning the value of the expression following the yield keyword. Once paused on a yield expression, the generator's code execution remains paused until the generator's next() method is called which moves on to return the next value as well as the completed status of the generator.

Use cases of generator functions

Generating unique IDs

A common use case for a generator function is generating a set of unique IDs, for example:

function *IdGenerator(){
  let id = 0;
 }
while(true){
  yield ++id;
}

const idIterator = IdGenerator()

const firstId = idIterator.next().value 
console.log(firstId) // 1
const secondId = idIterator.next().value
console.log(secondId) // 2
const thirdId = idIterator.next().value
console.log(thirdId) // 3

Using generator functions in async code

generator functions can also be used for sync code for more readable syntax and effective error handling, an example is shown below

'use strict';

async function* run() {
  await new Promise(resolve => setTimeout(resolve, 100));
  yield 'new';
  console.log('Value generated');
}

const asyncIterator = run();

// Prints "new\nValue generated"
(async () => {
  for await (const val of asyncIterator) {
    console.log(val); // Prints "new"
  }
})();

In conclusion

Writing infinite loops isn’t something that we generally want to do in a standard function. But with generators, everything is fine! Whenever the generator encounters a yield statement, the generator execution is suspended until the next method is called again. So every next() call executes only one iteration of our infinite while loop and sends back the next value which makes it easier to manage our returned values and gives us more control over how we treat each yielded value.