Skip to main content

Command Palette

Search for a command to run...

Asynchronous JavaScript

Published
•9 min read•View as Markdown

Asynchronous JavaScript is a programming technique used to handle tasks that take time to complete without blocking the execution of other code. This is crucial for creating responsive and efficient web applications, where certain tasks like network requests or file operations might take a while.

1. Synchronous vs. Asynchronous Execution

  • Synchronous Code: Executes line by line, where each task waits for the previous one to complete. This can make applications feel unresponsive if a task takes too long.

  • Example:

  •   console.log('Start');
      // Simulate a delay
      for (let i = 0; i < 1e9; i++) {}
      console.log('End');
    

    In this example, "End" will only print after the loop finishes, blocking other operations.

  • Asynchronous Code: Allows tasks to run independently of the main thread, so the program can continue executing other code while waiting for a task to complete.

    Example:

  •   console.log('Start');
      setTimeout(() => console.log('End'), 2000);
      console.log('Middle');
    

    Here, "End" prints after 2 seconds, while "Middle" prints immediately after "Start", making the code run in a non-blocking manner.

2. Ways to Handle Asynchronous Code

1.Callbacks: Functions passed as arguments to other functions, to be called later when an operation completes.

Example:

function fetchData(callback) {
  setTimeout(() => callback('Data received'), 2000);
}

fetchData((data) => console.log(data));

Callback Hell: This refers to the complex, nested callbacks that can make code hard to read and maintain.

2.Promises: Objects that represent the eventual outcome (success or failure) of an asynchronous operation. They provide a cleaner and more manageable way to handle asynchronous operations compared to callbacks.

Example:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Data received'), 2000);
});

promise.then(data => console.log(data));

3.Async/Await: Syntax that makes working with promises easier and more readable. It allows you to write asynchronous code in a synchronous style.

Example:

async function fetchData() {
  try {
    let response = await new Promise((resolve) => {
      setTimeout(() => resolve('Data received'), 2000);
    });
    console.log(response);
  } catch (error) {
    console.error('Error:', error);
  }
}

fetchData();

3. Key Concepts in Asynchronous JavaScript

  • Call Stack: Keeps track of function calls. When a function is called, it’s added to the stack. When it returns, it’s removed from the stack.

  • Event Loop: A system that continuously checks if the call stack is empty and moves tasks from the task queue to the call stack for execution. This allows JavaScript to handle asynchronous tasks like I/O operations without blocking the main thread.

  • Task Queue: Holds tasks (like network responses or timers) that are ready to be executed. Once the call stack is clear, the event loop moves tasks from this queue to the stack.

  • Web Browser APIs: Built-in functions provided by browsers for tasks like making network requests (e.g., Fetch API), interacting with the DOM, and handling timers.

Promise

What is a Promise?

A promise is an object representing the eventual completion (or failure) of an asynchronous operation. It provides a way to handle asynchronous operations more predictably and cleanly compared to traditional callbacks.

States of a Promise

A promise can be in one of three states:

  1. Pending: The initial state. The operation has not yet completed.

  2. Fulfilled: The operation completed successfully.

  3. Rejected: The operation failed, and an error occurred.Why Use Promises?

  • Avoid Callback Hell: Promises help manage multiple asynchronous operations without nesting callbacks.

  • Improved Readability: They make code more readable and maintainable by allowing chaining and handling errors in a more structured way.

Example:

let promise = new Promise((resolve, reject) => {
  let success = true; // Change to false to test rejection
  if (success) {
    resolve('Operation was successful!');
  } else {
    reject('Operation failed.');
  }
});

Creating a Promise

To create a promise, you use the Promise constructor, which takes a function with two parameters: resolve and reject.

Example:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Data loaded'), 2000); // Simulate async operation
});

Consuming a Promise

Once you have a promise, you can consume it using .then(), .catch(), and .finally().

Example:

promise
  .then(data => console.log(data)) // Handle successful result
  .catch(error => console.error(error)) // Handle errors
  .finally(() => console.log('Operation completed')); // Always executed

Chaining Promises

You can chain multiple .then() calls to perform a sequence of operations. Each .then() returns a new promise, allowing for continued chaining.

Example:

promise
  .then(data => {
    console.log(data);
    return 'Next step';
  })
  .then(result => console.log(result))
  .catch(error => console.error(error));

Handling Errors in Promises

Errors can be handled in a promise chain using .catch(). It’s essential to handle errors to avoid unhandled promise rejections.

Example:

promise
  .then(data => {
    throw new Error('Something went wrong');
  })
  .catch(error => console.error('Caught error:', error));

Without .catch(): If you don’t handle errors, they will propagate and potentially crash the application.

Promise-Based Functions

JavaScript provides several built-in promise-based functions:

  • 1. Promise.resolve(value): Returns a promise that is resolved with the given value.

    Example:

  •   Promise.resolve('Immediate value').then(console.log);
    

2. Promise.reject(reason): Returns a promise that is rejected with the given reason.

Example:

Promise.reject('Immediate error').catch(console.error);

3.Promise.all(iterable): Waits for all promises to resolve and returns an array of their results. If any promise rejects, the whole promise rejects.

Example

Promise.all([Promise.resolve(1), Promise.resolve(2)])
  .then(results => console.log(results)); // [1, 2]

4.Promise.any(iterable): Returns a promise that resolves as soon as one of the promises in the iterable fulfills, or rejects if no promises fulfill.

Example:

Promise.any([Promise.reject('Error'), Promise.resolve(1)])
  .then(result => console.log(result)); // 1

5.Promise.race(iterable): Returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects.

Example:

Promise.race([Promise.resolve(1), new Promise((_, reject) => setTimeout(() => reject('Error'), 500))])
  .then(result => console.log(result))
  .catch(error => console.error(error)); // 1

What is async/await?

async and await are syntactic sugars in JavaScript designed to simplify working with promises and asynchronous code. They provide a more readable and intuitive way to handle asynchronous operations, making your code look more like synchronous code while still being non-blocking.

How It Works

  1. async Function: A function declared with the async keyword. It always returns a promise. If the function returns a value, that value is automatically wrapped in a promise.

    Example:

async function fetchData() {
  return 'Data'; // Automatically wrapped in a promise
}

fetchData().then(data => console.log(data)); // Logs 'Data'

2 await Expression: Used inside an async function to pause the execution of the function until the promise resolves. It makes asynchronous code look and behave like synchronous code.

Example:

async function fetchData() {
  let data = await new Promise((resolve) => {
    setTimeout(() => resolve('Data received'), 2000);
  });
  console.log(data); // Logs 'Data received'
}

fetchData();

What is async/await?

async and await are syntactic sugars in JavaScript designed to simplify working with promises and asynchronous code. They provide a more readable and intuitive way to handle asynchronous operations, making your code look more like synchronous code while still being non-blocking.

How It Works

  1. async Function: A function declared with the async keyword. It always returns a promise. If the function returns a value, that value is automatically wrapped in a promise.

    Example:

     javascriptCopy codeasync function fetchData() {
       return 'Data'; // Automatically wrapped in a promise
     }
    
     fetchData().then(data => console.log(data)); // Logs 'Data'
    
  2. await Expression: Used inside an async function to pause the execution of the function until the promise resolves. It makes asynchronous code look and behave like synchronous code.

    Example:

     javascriptCopy codeasync function fetchData() {
       let data = await new Promise((resolve) => {
         setTimeout(() => resolve('Data received'), 2000);
       });
       console.log(data); // Logs 'Data received'
     }
    
     fetchData();
    

Why Use async/await?

  1. Simplified Syntax: async/await allows you to write asynchronous code that is easier to read and understand compared to chaining .then() methods.

    Example:

     // With Promises
     fetchData()
       .then(data => {
         return processData(data);
       })
       .then(result => {
         console.log(result);
       })
       .catch(error => {
         console.error('Error:', error);
       });
    
     // With async/await
     async function handleData() {
       try {
         let data = await fetchData();
         let result = await processData(data);
         console.log(result);
       } catch (error) {
         console.error('Error:', error);
       }
     }
    
     handleData();
    
    1. Error Handling: Using async/await with try...catch blocks for error handling is straightforward and mimics synchronous error handling, making it easier to catch and handle errors.

      Example :


    async function fetchData() {
      try {
        let data = await fetch('https://api.example.com/data');
        let json = await data.json();
        console.log(json);
      } catch (error) {
        console.error('Failed to fetch data:', error);
      }
    }

    fetchData();

3. Control Flow: async/await makes managing complex asynchronous logic easier by allowing you to write code in a sequential manner, even though it is executed asynchronously.

Example:

async function processTasks() {
  let task1 = await performTask1();
  let task2 = await performTask2(task1);
  let result = await performTask3(task2);
  console.log(result);
}

processTasks();

What is async/await?

async and await are syntactic sugars in JavaScript designed to simplify working with promises and asynchronous code. They provide a more readable and intuitive way to handle asynchronous operations, making your code look more like synchronous code while still being non-blocking.

How It Works

  1. async Function: A function declared with the async keyword. It always returns a promise. If the function returns a value, that value is automatically wrapped in a promise.

    Example:

     javascriptCopy codeasync function fetchData() {
       return 'Data'; // Automatically wrapped in a promise
     }
    
     fetchData().then(data => console.log(data)); // Logs 'Data'
    
  2. await Expression: Used inside an async function to pause the execution of the function until the promise resolves. It makes asynchronous code look and behave like synchronous code.

    Example:

     javascriptCopy codeasync function fetchData() {
       let data = await new Promise((resolve) => {
         setTimeout(() => resolve('Data received'), 2000);
       });
       console.log(data); // Logs 'Data received'
     }
    
     fetchData();
    

Why Use async/await?

  1. Simplified Syntax: async/await allows you to write asynchronous code that is easier to read and understand compared to chaining .then() methods.

    Example:

     javascriptCopy code// With Promises
     fetchData()
       .then(data => {
         return processData(data);
       })
       .then(result => {
         console.log(result);
       })
       .catch(error => {
         console.error('Error:', error);
       });
    
     // With async/await
     async function handleData() {
       try {
         let data = await fetchData();
         let result = await processData(data);
         console.log(result);
       } catch (error) {
         console.error('Error:', error);
       }
     }
    
     handleData();
    
  2. Error Handling: Using async/await with try...catch blocks for error handling is straightforward and mimics synchronous error handling, making it easier to catch and handle errors.

    Example:

     javascriptCopy codeasync function fetchData() {
       try {
         let data = await fetch('https://api.example.com/data');
         let json = await data.json();
         console.log(json);
       } catch (error) {
         console.error('Failed to fetch data:', error);
       }
     }
    
     fetchData();
    
  3. Control Flow: async/await makes managing complex asynchronous logic easier by allowing you to write code in a sequential manner, even though it is executed asynchronously.

    Example:

     javascriptCopy codeasync function processTasks() {
       let task1 = await performTask1();
       let task2 = await performTask2(task1);
       let result = await performTask3(task2);
       console.log(result);
     }
    
     processTasks();
    

Key Points

  • Always Use await Inside async Functions: await can only be used inside functions declared with async.

  • Error Handling: Use try...catch for error handling in async functions.

  • Parallel Execution: If you need to run multiple asynchronous operations in parallel, use Promise.all() with await.

    Example:

  •   async function fetchMultipleData() {
        try {
          let [data1, data2] = await Promise.all([
            fetchData1(),
            fetchData2()
          ]);
          console.log(data1, data2);
        } catch (error) {
          console.error('Error:', error);
        }
      }
    
      fetchMultipleData();