Header Ads Widget

WebLearner Pro Banner

Beginner to Advance HTML 17

 



Chapter 17: Asynchronous JavaScript – Understanding Callbacks, Promises, and Async/Await


JavaScript is primarily a single-threaded language, meaning it can execute one task at a time. However, modern web applications require handling tasks like fetching data from a server, processing user input, and performing background computations—all without freezing the user interface. Asynchronous JavaScript helps you manage these tasks efficiently, allowing the browser to stay responsive while performing time-consuming operations. In this chapter, we'll explore callbacks, Promises, and the async/await syntax to understand how to handle asynchronous programming in JavaScript.



---


1. Introduction to Asynchronous JavaScript


Asynchronous programming allows your code to run tasks in the background without blocking the main thread. For example, when you fetch data from an API, your application can continue to run smoothly without waiting for the data to load.


2. Callbacks


A callback is a function that is passed as an argument to another function and is executed after the completion of that function. Callbacks were the original method for handling asynchronous operations in JavaScript.


Example: Using a Callback Function


function fetchData(callback) {

    setTimeout(() => {

        console.log("Data fetched from server");

        callback();

    }, 2000);

}


function processData() {

    console.log("Processing data...");

}


fetchData(processData);


Explanation:


fetchData() simulates a server request using setTimeout().


After fetching data, it calls processData() as a callback.



Drawback of Callbacks: Callback Hell


When you have nested callbacks, your code can become difficult to read and maintain—a situation known as callback hell.


setTimeout(() => {

    console.log("Step 1");

    setTimeout(() => {

        console.log("Step 2");

        setTimeout(() => {

            console.log("Step 3");

        }, 1000);

    }, 1000);

}, 1000);


3. Introduction to Promises


Promises are a cleaner way to handle asynchronous operations and avoid callback hell. A Promise is an object representing the eventual completion (or failure) of an asynchronous operation.


Creating a Promise


A Promise has three states:


Pending: Initial state, neither fulfilled nor rejected.


Fulfilled: Operation completed successfully.


Rejected: Operation failed.



const myPromise = new Promise((resolve, reject) => {

    let success = true;

    setTimeout(() => {

        if (success) {

            resolve("Promise fulfilled");

        } else {

            reject("Promise rejected");

        }

    }, 2000);

});


myPromise

    .then((result) => console.log(result))

    .catch((error) => console.error(error))

    .finally(() => console.log("Operation complete"));


Explanation:


The resolve() function is called if the operation is successful.


The reject() function is called if there’s an error.


The then(), catch(), and finally() methods are used to handle the outcome.



Chaining Promises


Promises can be chained to handle multiple asynchronous operations sequentially.


const step1 = () => Promise.resolve("Step 1 completed");

const step2 = () => Promise.resolve("Step 2 completed");

const step3 = () => Promise.resolve("Step 3 completed");


step1()

    .then((result) => {

        console.log(result);

        return step2();

    })

    .then((result) => {

        console.log(result);

        return step3();

    })

    .then((result) => console.log(result))

    .catch((error) => console.error(error));


4. The async/await Syntax


The async/await syntax is a modern way to handle asynchronous code. It is built on top of Promises and provides a cleaner, more readable way to write asynchronous operations.


Using async and await


An async function always returns a Promise.


The await keyword pauses the execution of the async function until the Promise resolves or rejects.



async function fetchData() {

    try {

        let response = await fetch('https://jsonplaceholder.typicode.com/posts/1');

        let data = await response.json();

        console.log(data);

    } catch (error) {

        console.error("Error:", error);

    } finally {

        console.log("Fetch attempt complete");

    }

}


fetchData();


Explanation:


The await keyword waits for the fetch() function to resolve.


If there’s an error, it’s caught in the catch block.


The finally block runs regardless of whether the operation succeeds or fails.



Handling Multiple Promises with Promise.all()


If you have multiple asynchronous operations, you can use Promise.all() to run them in parallel.


const fetchPost1 = fetch('https://jsonplaceholder.typicode.com/posts/1').then(res => res.json());

const fetchPost2 = fetch('https://jsonplaceholder.typicode.com/posts/2').then(res => res.json());


async function fetchPosts() {

    const results = await Promise.all([fetchPost1, fetchPost2]);

    console.log(results);

}


fetchPosts();


Explanation:


Promise.all() waits for all Promises to resolve or any one of them to reject.


It’s useful for optimizing performance when you have multiple independent asynchronous operations.



5. Error Handling in Asynchronous Code


Proper error handling is crucial in asynchronous code to prevent unexpected behavior.


Try-Catch Block: Use with async/await for error handling.


.catch() Method: Use with Promises for handling errors.



async function fetchData() {

    try {

        let response = await fetch('invalid-url');

        let data = await response.json();

        console.log(data);

    } catch (error) {

        console.error("Fetch failed:", error.message);

    }

}


fetchData();


6. Practical Example: Fetching Data from an API


Let's build a simple example where we fetch user data from a public API and display it on a web page.


HTML Structure:


<button id="loadUsers">Load Users</button>

<ul id="userList"></ul>


JavaScript Code:


document.getElementById('loadUsers').addEventListener('click', async () => {

    const userList = document.getElementById('userList');

    userList.innerHTML = 'Loading...';


    try {

        const response = await fetch('https://jsonplaceholder.typicode.com/users');

        const users = await response.json();

        userList.innerHTML = users.map(user => `<li>${user.name}</li>`).join('');

    } catch (error) {

        userList.innerHTML = 'Failed to load users';

        console.error("Error:", error);

    }

});


7. Best Practices for Asynchronous JavaScript


Use async/await for cleaner and more readable code.


Use Promise.all() to run multiple asynchronous tasks in parallel.


Always handle errors to prevent unexpected application behavior.


Use try-catch blocks with async/await for robust error handling.



Summary


In this chapter, you learned about asynchronous JavaScript, including callbacks, Promises, and the async/await syntax. Mastering

 these techniques is essential for building responsive and efficient web applications. In the next chapter, we’ll explore JavaScript’s capabilities for manipulating APIs, including using the Fetch API and handling JSON data.


Post a Comment

0 Comments