DEV Community

Dharan Ganesan
Dharan Ganesan

Posted on

Day 30: Generator

JavaScript generators are special functions that can be paused and resumed during their execution, allowing the producer of the generator to control the pace at which data is generated and consumed. They use the function* syntax and the yield keyword to pause ⏸️ the execution and yield values to the calling code.

Synchronous and Asynchronous Behavior

Generators can be used both synchronously and asynchronously, providing a flexible toolset for handling different types of operations.

  • πŸ”„ Synchronous Operations:
   function* syncGenerator() {
       yield 'Step 1';
       yield 'Step 2';
       yield 'Step 3';
   }
Enter fullscreen mode Exit fullscreen mode
  • ⏱️ Asynchronous Operations
   function fetchData(url) {
       return fetch(url).then(response => response.json());
   }

   async function* asyncGenerator() {
       yield await fetchData('url1');
       yield await fetchData('url2');
       yield await fetchData('url3');
   }
Enter fullscreen mode Exit fullscreen mode

πŸ“ž Bidirectional Communication

Generators allow bidirectional communication between the caller and the generator, making them a powerful tool for cooperative multitasking.

function* userPrompt() {
    const response = yield 'Please enter your name:';
    yield `Hello, ${response}! How can I assist you today?`;
}

const prompt = userPrompt();
console.log(prompt.next().value); // Output: Please enter your name:
console.log(prompt.next('Alice').value); // Output: Hello, Alice! How can I assist you today?
Enter fullscreen mode Exit fullscreen mode

❗ Error Handling

Generators provide elegant error handling through the throw method. This allows errors to be thrown inside the generator and caught outside it.

function* errorGenerator() {
    try {
        yield 'Step 1';
        throw new Error('Something went wrong');
    } catch (error) {
        yield `Caught an error: ${error.message}`;
    }
}

const iterator = errorGenerator();
console.log(iterator.next().value); // Output: Step 1
console.log(iterator.next().value); // Output: Caught an error: Something went wrong
Enter fullscreen mode Exit fullscreen mode

Design Patterns with Generators

Generators have given rise to several design patterns that simplify complex asynchronous code.

  • πŸ” Generator as Iterator
   const iterableObject = {
       *[Symbol.iterator]() {
           yield 1;
           yield 2;
           yield 3;
       }
   };
Enter fullscreen mode Exit fullscreen mode
  • βš™οΈ Generator as Control Flow
   async function* stepByStepAsync() {
       yield asyncOperation1();
       yield asyncOperation2();
       yield asyncOperation3();
   }
Enter fullscreen mode Exit fullscreen mode

Advantages of Generators

  • πŸ’Ύ Efficient Memory Usage: Generators produce values on-the-fly, which can significantly reduce memory usage when dealing with large datasets.
   // Generating an Infinite Sequence of Numbers
   function* infiniteNumbers() {
       let i = 0;
       while (true) {
           yield i++;
       }
   }
Enter fullscreen mode Exit fullscreen mode
  • ⏩ Lazy Evaluation: Values are generated only when requested, leading to better performance and reduced overhead.
   // Lazily Generating Fibonacci Numbers
   function* fibonacci() {
       let a = 0, b = 1;
       while (true) {
           yield a;
           [a, b] = [b, a + b];
       }
   }
Enter fullscreen mode Exit fullscreen mode

Top comments (0)