JavaScript Async and Iterators

JavaScript offers several powerful constructs for managing sequences and asynchronous operations: synchronous operations and iterators.

These features enhance the flexibility and readability of code, particularly when dealing with complex data flows and asynchronous programming.

Synchronous Operations

Synchronous operations in JavaScript are executed in a sequential manner, meaning each operation must complete before the next one starts.

This straightforward approach is easy to understand and implement but can be inefficient for operations that involve waiting, such as network requests or file I/O.

Example of Synchronous Code


console.log("Start");

function fetchData() {
  // Simulate a time-consuming task
  for(let i = 0; i < 1000000000; i++){ 
    return "Data fetched";
  }
}

const data = fetchData();

console.log(data);

console.log("End");
            

In the above example, the fetchDatafunction performs a time-consuming task.

The console logs Start, then the task is executed, and finally, Data fetchedand Endare logged.

The program waits for fetchDatato complete before moving to the next line.

Iterators

Iterators are objects that allow you to traverse through a collection of data, such as arrays or custom data structures.

An iterator implements the nextmethod, which returns an object with two properties: valueand done.

The valueproperty contains the current value, and doneindicates whether the iteration is complete.

Example of an Iterator


const array = [1, 2, 3];
const iterator = array[Symbol.iterator]();

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

In the above example, the arrayobject is iterated using its default iterator.

Each call to iterator.next()returns the next value in the array until all values are iterated, after which doneis true.

Using Generators for Iteration

Generators are particularly useful for creating custom iterators. They provide a more concise and readable way to define complex iteration behavior.

Example with Iterators


function* countUpTo (max) {
  for(let i = 0; i <= max; i++){ 
    yield i;
  }
}

const counter = countUpTo(5);

for(const value of counter){ 
  console.log(value); // 1 2 3 4 5
}

Here, countUpTois a generator function that yields values from 1 to max.

Using a for...ofloop, we can easily iterate over these values.

Asynchronous Generators

Asynchronous generators extend the capabilities of regular generators to handle asynchronous operations.

They are defined using async function*and can yield promises.

Example of an Asynchronous Generator


async function* fetchData () {
  let urls = [
    "https://jsonplaceholder.typicode.com/posts/1",
    "https://jsonplaceholder.typicode.com/posts/2",
    "https://jsonplaceholder.typicode.com/posts/3"
    ];

    for(const url of urls){ 
      const response= await fetch(url);
      const data= await response.json();
      yield data; 
    }
  }
  
  (async  () =>  {
    const asyncGen = fetchData();
    for wait(const value of asyncGen){ 
      console.log(value);
    }
  })()
          

In the above example, fetchDatais an asynchronous generator function that fetches data from multiple URLs.

The for await...ofloop is used to iterate over the yielded promises.

Understanding synchronous operations and iterators in JavaScript is crucial for writing efficient and readable code.

Synchronous operations are straightforward but can be limiting in certain scenarios.

Iterators provide a standardized way to traverse collections, while generators and asynchronous generators offer powerful capabilities for managing complex and asynchronous tasks.

Let's learn about Modules in the next chapter.