Generators in JavaScript: A Short Guide

Generators in JavaScript: A Short Guide

Hello guys, I will be talking about generators in this blog. Let's start with a simple introduction, then let's understand the actual use cases of generators.

Definition

Generators are a special type of function in JavaScript that allow you to control the flow of execution. Unlike regular functions that run to completion, generators can be paused in the middle of their execution and later resumed.

They are declared using the function* keyword.

Code

A typical generator code in JavaScript looks like this:

function* exampleGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const generator = exampleGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
/* after this everytime you call console.log(generator.next()), 
   you will get { value: undefined, done: true }
*/

The idea is simple, for every time you call generator.next(), the next value generated by the generator gets returned. And those values are generated by the yield keyword.

Example Use Case

A very basic example use case of the generators would be to find the Fibonacci Sequence.

Fibonacci Sequence if you don't know is a sequence of numbers in which the current number is equal to sum of previous two numbers, and the first 2 numbers are 0 and 1.

For example:

0, 1, 1, 2, 3, 5, 8, 13...

Here's the code for the same using generator.

function* fibGenerator() {
    let curr = 0;
    let next = 1;

    while(true) {
        yield curr;
        [curr, next] = [next, curr + next];
    }
}

const fibonacci = fibGenerator();

console.log(fibonacci.next().value); //0
console.log(fibonacci.next().value); //1
console.log(fibonacci.next().value); //1
console.log(fibonacci.next().value); //2
console.log(fibonacci.next().value); //3

The logic behind the above code is simple. We declared fibGenerator using the keyword function*. Then inside the function, we declared 2 variables curr and next, which correspond to the current and next number respectively. Then we create an infinite while loop so as to keep generating the next number in the fibonacci sequence. Then at each iteration, we yield the curr value and then update curr to next value and next to curr + next value.

Usage in TypeScript

In TypeScript, the Generator type is a built-in type that represents a generator function. The Generator type takes three type parameters:

  1. Yield Type (YieldType): This is the type of values that the generator yields using the yield keyword.

  2. Return Type (ReturnType): This is the type of the value returned by the generator when it completes using a return statement.

  3. Next Type (NextType): This is the type of the argument that can be passed to the next method when iterating over the generator.

Example Syntax:

function* exampleGenerator(): Generator<number, undefined, undefined> {
    yield 1;
    yield 2;
    yield 3;
}

const generator: Generator<number, undefined, undefined> = exampleGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
/* after this, every time you call console.log(generator.next()), 
   you will get { value: undefined, done: true }
*/

Fibonacci code in TypeScript

function* fibGenerator(): Generator<number, void, unknown> {
    let curr = 0;
    let next = 1;

    while (true) {
        yield curr;
        [curr, next] = [next, curr + next];
    }
}

const fibonacci: Generator<number, void, unknown> = fibGenerator();

console.log(fibonacci.next().value); // 0
console.log(fibonacci.next().value); // 1
console.log(fibonacci.next().value); // 1
console.log(fibonacci.next().value); // 2
console.log(fibonacci.next().value); // 3

Real World Use Case

Now, you might wonder if these generators have a real world use case. They sure do have. One real-world use case for generators in JavaScript/TypeScript is in managing asynchronous control flow. Generators can simplify asynchronous code by allowing you to write asynchronous logic in a more synchronous style, making it easier to understand and maintain.

Consider an example using the fetch API to make multiple asynchronous requests sequentially. In this case, the generator function can yield promises, and the control flow will pause until the promises are resolved.

function* fetchData(): Generator<Promise<any>, void, unknown> {
    try {
        const response1 = yield fetch('https://api.example.com/data1');
        const data1 = yield response1.json();

        const response2 = yield fetch('https://api.example.com/data2');
        const data2 = yield response2.json();

        console.log('Data 1:', data1);
        console.log('Data 2:', data2);

    } catch (error) {
        console.error('Error fetching data:', error);
    }
}

function runGenerator(generator: Generator<Promise<any>, void, unknown>) {
    const iterate = (result: IteratorResult<Promise<any>, void>): void => {
        if (result.done) {
            return;
        }

        result.value
            .then((data) => iterate(generator.next(data)))
            .catch((error) => iterate(generator.throw(error)));
    };

    iterate(generator.next());
}

// Usage
const dataGenerator = fetchData();
runGenerator(dataGenerator);

In conclusion, generators in JavaScript are special functions that as the name says work as generators, i.e., generate something accordingly. And for generating some kind of result they use the yield keyword.


I hope you learned something new today. You can reach out to me on:

  1. Twitter

  2. Showwcase

Did you find this article valuable?

Support Swapnil by becoming a sponsor. Any amount is appreciated!