A JavaScript generator is a special function that lets you control when it returns data. Instead of just returning one value, it can return many.

Let’s create a generator

To make a generator in JavaScript, we use a specific syntax that includes the keyword “function*”

Check the example below.

function* generateNames() { 
  yield "John"; 
  yield "Mary"; 
  return "Doe";
}

When the generateNames function is invoked, it does not execute right away. Instead, it produces a special object known as a ‘generator’ object, which differs from the anticipated return values.

const genNames = generateNames()

This ‘generator’ object provides a series of instance methods which you can call.

One of them is the next() method – when called, it returns the next ‘yield’ value. For example, running the line of code below would result in “John”, and “Mary” as the value for their “value” properties.

genNames().next() // returns { value: "John", done: false }
genNames().next() // returns { value: "Mary", done: false }

When using the “next” method, it returns an object that has two properties:
– “value” which holds the value that was returned
– “done” which is set to true if the function has finished running and returned all its values, or to false if it hasn’t finished yet.

When we call the next() method on the generator to retrieve the last value, we obtain

genNames().next() // returns { value: "Doe", done: true }

If we attempt to call the next() method again, we will get

genNames().next() // returns { value: undefined, done: true }

Generators can be iterated over using the next() method, as they are iterable.

For example –

function* generateNames() {
  yield "John";
  yield "Mary";
  return "Doe";
}

let genNames = generateNames();

for(let value of genNames) {
  console.log(value); // "John", then "Mary"
}

Isn’t it much nicer to work with generators using for-of iteration instead of calling .next().value? But, please keep in mind that the example above only shows “John” and “Mary”, and not “Doe”. This happens because the for-of loop ignores the last value when it’s done, so if we want all the results to be shown using for..of, we need to return them with yield.

function* generateNames() {
  yield "John";
  yield "Mary";
  yield "Doe";
}

let genNames = genNames();

for (let value of genNames) {
  console.log(value); // "John", then "Mary", then "Doe"
}

As generators are iterable, we can utilize all related functionality, such as the spread operator(…)

function* generateNames() {
  yield "John";
  yield "Mary";
  yield "Doe";
}

let names = ["Peter", …generateNames()];

console.log(names); // "Peter", "John", "Mary", "Doe"

Generator Composition

You can combine generators inside each other using generator composition.

For example

function* generateNum(start, end) {
  for (let i = start; i <= end; i++) yield i;
}

function* generateNumbers() {
  yield* generateNum(1, 10);
  yield* generateNum(11, 20);
  yield* generateNum(21, 30);
}

let total = 0;

for(let num of generateNumbers()) {
  total += num
}

console.log(total); // 465

The special “yield*” directive in the example is in charge of composition. It sends the task to another generator. In simpler terms, it runs generators and passes their results on as if they came from the main generator.

Passing values to generators

Generators not only give us a result, but they can also receive a value and work with it.

In order to do this, we need to use generator.next(arg) and provide an argument. This argument will be treated as the outcome of the yield statement.

Checkout the example below:

function* generator() {
  let result = yield "How old are you?";
  console.log(result);
}

let gen = generator();

let question = gen.next().value;

gen.next(4);

When you use gen.next(), it kicks off the process and gives you the first result (“How old are you?”). Then the generator pauses. When you use gen.next(4), it resumes and returns 4 as the result.

In conclusion, JavaScript generators provide a powerful way to control the flow of data and create iterable sequences. By utilizing the “yield” keyword, generators can produce multiple values and interact with other generators through composition. Generators offer flexibility in consuming and producing values, and their ability to pause and resume execution makes them a valuable tool for managing asynchronous operations. Additionally, the simplicity and readability of working with generators using the for-of loop or the spread operator make them an attractive feature in JavaScript for managing sequences of data. Overall, generators are a valuable addition to JavaScript’s capabilities, enabling more efficient and expressive code.

Categorized in: