• JavaScript Video Tutorials

JavaScript - Async Iteration



Asynchronous Iteration

In JavaScript, asynchronous iteration refers to the ability to iterate over asynchronous sequences or collections, such as those returned by asynchronous functions or generators. Async iteration is typically used with operations that involve asynchronous tasks, such as fetching data from a remote server or reading from a file.

Understanding Asynchronous Operations

In basic terms, asynchronous operations in programming denote tasks or procedures that do not obstruct the program's execution during their pending completion. Rather than pausing for each operation to conclude before proceeding onto the subsequent one; these asynchronous tasks enable a program: it continues executing other duties, concurrently waiting for the current task's finalization.

Using the 'for await...of' Loop

The for await...of loop is used for asynchronous iteration. It works similarly to the regular for...of loop, but it is designed to work with asynchronous iterators. An asynchronous iterator is an object that defines an async next() method, which returns a promise for the next value in the sequence.

Example: Using Promises

JavaScript incorporates promises as a characteristic to manage asynchronous operations; these promises symbolize the potential outcomes, either completion or failure of an asynchronous task. Notably, the function asyncOperation emulates such tasks by returning a promise. The 'for await...of' loop elegantly navigates the asynchronous sequence, emphasizing promise utilization in managing non-blocking operations without compromising code lucidity.

<!DOCTYPE html>
<html>
<body>
<h2>Async Iteration with Promises</h2>
<div id="output"></div>
<script>
  function asyncOperation(value) {
    return new Promise(resolve => {
      setTimeout(() => {
        document.getElementById('output').innerHTML += `<p>Processed: ${value}</p>`;
        resolve(value);
      }, 1000);
    });
  }

  const asyncIterable = {
    [Symbol.asyncIterator]: async function* () {
      for (let i = 1; i <= 3; i++) {
        yield await asyncOperation(i);
      }
    },
  };

  async function processAsyncIterable() {
    for await (const result of asyncIterable) {
      document.getElementById('output').innerHTML += `<p>Received: ${result}</p>`;
    }
  }

  processAsyncIterable();
</script>
</body>
</html>

Example 2: Using Fetch API for Asynchronous HTTP Requests

Here, we demonstrate asynchronous iteration with the Fetch API for executing HTTP requests: The asyncIterable operates to fetch data in an asynchronous manner. Furthermore; employing a 'for await...of' loop - it elegantly traverses through results showcasing how seamlessly async iteration amalgamates with external source data retrieval.

<!DOCTYPE html>
<html>
<body>
<h2>Async Iteration with Fetch API</h2>
<div id="output"></div>
<script>
  const url = 'https://jsonplaceholder.typicode.com/todos/';
  const asyncIterable = {
    [Symbol.asyncIterator]: async function* () {
      for (let i = 1; i <= 3; i++) {
        const response = await fetch(`${url}${i}`);
        const data = await response.json();
        document.getElementById('output').innerHTML += `<p>Received: ${JSON.stringify(data)}</p>`;
        yield data;
      }
    },
  };

  async function processAsyncIterable() {
    for await (const result of asyncIterable) {
      // Already displaying results above, no need for additional output.
    }
  }

  processAsyncIterable();
</script>
</body>
</html>

Example 3: Using callback

The approach employs a callback-based mechanism to achieve asynchronous iteration. The function asyncOperation imitates an asynchronous task and calls back upon completion. Meanwhile, the processAsyncIterable function actively iterates through an array, invoking the asynchronous operation for every element.

<!DOCTYPE html>
<html>
<body>
<h2>Async Iteration with callback</h2>
<div id="output"></div>
<script>
  function asyncOperation(value, callback) {
    setTimeout(() => {
      document.getElementById('output').innerHTML += `<p>Processed: ${value}</p>`;
      callback(value);
    }, 1000);
  }

  function processAsyncIterable(iterable, callback) {
    const iterator = iterable[Symbol.iterator]();  
    function iterate() {
      const next = iterator.next();
      if (next.done) {        
        return;
      }

      const value = next.value;
      asyncOperation(value, result => {
        document.getElementById('output').innerHTML += `<p>Received: ${result}</p>`;
        iterate(); 
      });
    }
    iterate(); 
  }

  const asyncIterable = [5,6,7,8,9,10]; 
  processAsyncIterable(asyncIterable, result => {
    // You can handle final result or additional actions here if needed.
  });
</script>
</body>
</html>

Example 4: Promise With Error

The method .then() in JavaScript employs one or two callback functions to manage the successful resolution of a Promise: upon the promise's resolution, it executes its first function; should rejection occur – an optional second function is then executed.

The method .catch() accompanies Promises, specifically to address promise rejections. A single callback function executes upon the rejection of the promise; this provides an elegant solution for managing errors in asynchronous operations - eliminating the need for a distinct .then() block dedicated to error handling.

<!DOCTYPE html>
<html>
<head>
  <style>
    #output {
      margin-top: 20px;
    }
  </style>
</head>
<body>
<h2>Async Iteration with Promises</h2>
<button onclick="startAsyncIteration()">Start Async Iteration</button>
<div id="output"></div>
<script>
  function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  function fetchData(index) {
    return new Promise((resolve, reject) => {
      if (index < 5) {
        delay(1000).then(() => resolve(`Data ${index}`));
      } else {
        // Simulate an error for index 5
        reject(new Error('Error fetching data for index 5'));
      }
    });
  }

  function startAsyncIteration() {
    document.getElementById('output').innerHTML = '';
    let index = 0;
    function iterate() {
      fetchData(index)
        .then(data => {
          displayData(data);
          index++;
          if (index < 6) {
            iterate();
          }
        })
        .catch(error => {
          // Display error on the page.
          displayError(error.message);
        });
    }
    iterate();
  }

  function displayData(data) {
    const outputDiv = document.getElementById('output');
    outputDiv.innerHTML += `<p>Data received: ${data}</p>`;
  }

  function displayError(errorMessage) {
    const outputDiv = document.getElementById('output');
    outputDiv.innerHTML += `<p style="color: red;">Error: ${errorMessage}</p>`;
  }
</script>
</body>
</html>

Real World Use Cases

In real-world scenarios, we apply JavaScript async iterations to optimize various asynchronous operations: fetching data concurrently from multiple APIs in web applications; processing real-time updates - a function critical for chat systems and executing batch tasks or parallel tasks that require intensive resources. Furthermore, managing file operations and streams is possible with this technique along with handling concurrent user interactions on interactive web pages. Other applications involve processing data from IoT devices dynamically loading content onto webpages, these too benefit greatly from the use of asynchronous iteration due to their need for non-blocking efficiency as well responsiveness when dealing with complex task management such as offline-first application's data synchronization.

Advertisements