JavaScript - Generators

JavaScript Generators are special types of functions that allow you to pause and resume execution. Unlike regular functions that run to completion once called, generators provide more control by producing values one at a time, on-demand, using the yield keyword.

1. What Are Generators?

Generators are functions that:

Can pause execution at any point using yield.

Can resume execution where they left off.

Return an iterator object when invoked.

Generators provide a powerful way to handle asynchronous operations, iterators, and infinite sequences.

2. Syntax of a Generator Function

Generator functions are defined using the function* syntax. The * indicates that the function is a generator.

Example:

function* generatorFunction() {

    yield "First value";

    yield "Second value";

    return "Final value";

}

3. Using yield in Generators

The yield keyword is used to pause the execution of the generator and return a value.

Example:

function* greet() {

    yield "Hello";

    yield "How are you?";

    yield "Goodbye!";

}

const generator = greet();

console.log(generator.next().value); // Logs: "Hello"

console.log(generator.next().value); // Logs: "How are you?"

console.log(generator.next().value); // Logs: "Goodbye!"

console.log(generator.next().value); // Logs: undefined (no more yields)

4. The next() Method

The generator object returned by a generator function implements the iterator protocol. The next() method:

Resumes execution until the next yield.

Returns an object with two properties:

value: The value of the current yield expression.

done: A boolean indicating if the generator has completed execution.

Example:

function* numbers() {

    yield 1;

    yield 2;

    yield 3;

}

const iterator = numbers();

console.log(iterator.next()); // { value: 1, done: false }

console.log(iterator.next()); // { value: 2, done: false }

console.log(iterator.next()); // { value: 3, done: false }

console.log(iterator.next()); // { value: undefined, done: true }

5. Generator Example: Infinite Sequence

Generators can create infinite sequences, which are useful for tasks like pagination or streaming data.

Example:

function* infiniteNumbers() {

    let number = 1;

    while (true) {

        yield number++;

    }

}

const sequence = infiniteNumbers();

console.log(sequence.next().value); // Logs: 1

console.log(sequence.next().value); // Logs: 2

console.log(sequence.next().value); // Logs: 3

6. Delegating Generators

Generators can delegate control to another generator using the yield* keyword.

Example:

function* subGenerator() {

    yield "Sub-generator value 1";

    yield "Sub-generator value 2";

}

function* mainGenerator() {

    yield "Main generator value";

    yield* subGenerator();

    yield "Another main generator value";

}

const iterator = mainGenerator();

for (const value of iterator) {

    console.log(value);

}

// Logs:

// "Main generator value"

// "Sub-generator value 1"

// "Sub-generator value 2"

// "Another main generator value"

7. Use Cases of Generators

a. Asynchronous Programming

Generators can be used with Promise or libraries like co to handle asynchronous tasks in a sequential manner.

function* asyncGenerator() {

    console.log("Start");

    yield new Promise((resolve) => setTimeout(() => resolve("First async value"), 1000));

    yield new Promise((resolve) => setTimeout(() => resolve("Second async value"), 2000));

}

const generator = asyncGenerator();

generator.next().value.then(console.log); // Logs: "First async value"

generator.next().value.then(console.log); // Logs: "Second async value"

b. Lazy Evaluation

Generators produce values on demand, making them ideal for large data sets or sequences.

c. Iterating Over Data

Generators can replace complex iterators for custom looping logic.

8. Conclusion

JavaScript generators are a versatile and powerful feature for managing control flow, particularly in asynchronous programming and lazy evaluation. With their ability to pause and resume execution, generators open up new possibilities for clean and efficient code.

Key Points:

Generators use function* and yield.

They return an iterator object with the next() method.

Generators are ideal for asynchronous operations, lazy evaluation, and custom iterators.