Day 30: Generator
Tags: day30, javascript, 100daysofcode
Published: 2023-08-21 13:30:00

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**: 

   ```javascript
   function* syncGenerator() {
       yield 'Step 1';
       yield 'Step 2';
       yield 'Step 3';
   }
   ```

- ⏱️ **Asynchronous Operations**

   ```javascript
   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');
   }
   ```

## 📞 Bidirectional Communication

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

```javascript
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?
```

##❗ Error Handling

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

```javascript
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
```

## Design Patterns with Generators

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

-  🔁 **Generator as Iterator**

   ```javascript
   const iterableObject = {
       *[Symbol.iterator]() {
           yield 1;
           yield 2;
           yield 3;
       }
   };
   ```

- ⚙️ **Generator as Control Flow**

   ```javascript
   async function* stepByStepAsync() {
       yield asyncOperation1();
       yield asyncOperation2();
       yield asyncOperation3();
   }
   ```

## Advantages of Generators

- 💾 **Efficient Memory Usage**: Generators produce values on-the-fly, which can significantly reduce memory usage when dealing with large datasets. 
   
   ```javascript
   // Generating an Infinite Sequence of Numbers
   function* infiniteNumbers() {
       let i = 0;
       while (true) {
           yield i++;
       }
   }
   ```

- ⏩ **Lazy Evaluation**: Values are generated only when requested, leading to better performance and reduced overhead. 

   ```javascript
   // Lazily Generating Fibonacci Numbers
   function* fibonacci() {
       let a = 0, b = 1;
       while (true) {
           yield a;
           [a, b] = [b, a + b];
       }
   }
   ```