DEV Community

francesco agati
francesco agati

Posted on

1

Generators in JavaScript

Generators in JavaScript are a powerful feature introduced in ECMAScript 6 (ES6) that allow you to define iterative algorithms by pausing and resuming execution at defined points. They provide a flexible way to control iteration and asynchronous operations.

How Generators Work

Generators are defined using function* syntax (note the asterisk). They use the yield keyword to pause execution and return values sequentially. When called, a generator function returns an iterator object that can be used to control the iteration.

Let's explore some practical examples to understand how generators are used:

Example 1: Fibonacci Sequence Generator

function* fibonacciGenerator() {
    let prev = 0;
    let curr = 1;

    yield prev;
    yield curr;

    while (true) {
        let next = prev + curr;
        yield next;
        prev = curr;
        curr = next;
    }
}

// Usage
const fibonacciSequence = fibonacciGenerator();
for (let i = 0; i < 10; i++) {
    console.log(fibonacciSequence.next().value); // Outputs: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}
Enter fullscreen mode Exit fullscreen mode

In this example, fibonacciGenerator generates an infinite sequence of Fibonacci numbers, pausing after each yield statement until next() is called again.

Example 2: Inorder Traversal of a Binary Tree

class Node {
    constructor(value) {
        this.value = value;
        this.left = null;
        this.right = null;
    }
}

function* inorderTraversal(node) {
    if (node !== null) {
        yield* inorderTraversal(node.left);   // Traverse left subtree
        yield node.value;                     // Yield current node's value
        yield* inorderTraversal(node.right);  // Traverse right subtree
    }
}

// Usage
const rootNode = new Node(10);
rootNode.left = new Node(5);
rootNode.right = new Node(15);
// (Set up the rest of the tree as in the example)

const iterator = inorderTraversal(rootNode);
const result = [];
for (let value of iterator) {
    result.push(value);
}
console.log(result); // Outputs: [3, 5, 7, 10, 15, 18]
Enter fullscreen mode Exit fullscreen mode

This generator function performs an inorder traversal of a binary tree, yielding values in the correct order.

Example 3: Custom Iterator Using Generators

class Range {
    constructor(start, end) {
        this.start = start;
        this.end = end;
    }

    *[Symbol.iterator]() {
        for (let i = this.start; i <= this.end; i++) {
            yield i;
        }
    }
}

// Usage
const range = new Range(1, 5);
for (let num of range) {
    console.log(num); // Outputs: 1, 2, 3, 4, 5
}
Enter fullscreen mode Exit fullscreen mode

Here, the Range class uses a generator to create an iterable range of numbers from start to end.

Example 4: Asynchronous Control Flow

function fetchDataFromAPI(endpoint) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const data = { endpoint, results: [1, 2, 3, 4, 5] };
            resolve(data);
        }, Math.random() * 1000);
    });
}

function* fetchAndProcessData() {
    try {
        const data1 = yield fetchDataFromAPI('/api/data1');
        console.log('Processed data1:', data1);

        const data2 = yield fetchDataFromAPI('/api/data2');
        console.log('Processed data2:', data2);

        const data3 = yield fetchDataFromAPI('/api/data3');
        console.log('Processed data3:', data3);

        console.log('All data processed!');
    } catch (error) {
        console.error('Error:', error);
    }
}

function runGenerator(generator) {
    const iterator = generator();

    function iterate(iteration) {
        if (iteration.done) {
            return iteration.value;
        }

        const promise = iteration.value;
        return promise.then((value) => iterate(iterator.next(value)))
                      .catch((error) => iterator.throw(error));
    }

    return iterate(iterator.next());
}

// Usage
runGenerator(fetchAndProcessData);
Enter fullscreen mode Exit fullscreen mode

This example demonstrates using a generator to manage asynchronous operations sequentially, yielding promises and processing their results.

Conclusion

Generators offer a flexible way to control iteration and manage asynchronous flows in JavaScript. By pausing execution with yield, they allow complex tasks to be handled in a more readable and sequential manner. Understanding generators enhances your ability to write efficient and expressive JavaScript code.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay