JavaScript is single-threaded, meaning it can only do one thing at a time. However, with asynchronous programming, JavaScript can perform non-blocking operations, allowing other code to run while waiting for long-running tasks to complete. Below are examples and detailed explanations of different async patterns in JavaScript.
-
Synchronous vs Asynchronous Execution:
Synchronous code executes line by line, meaning each line waits for the previous one to finish before executing. Asynchronous code, however, allows certain tasks (like network requests) to happen in the background without blocking the execution of other code.
console.log("Start"); setTimeout(() => { console.log("Async Operation"); }, 2000); // This executes after 2 seconds console.log("End");
Explanation:
Startis logged first.- The
setTimeoutis asynchronous, so it schedules the operation to run in 2 seconds and allows the program to continue. Endis logged immediately afterStart.- After 2 seconds, the
Async Operationis logged.
-
Callbacks:
A callback function is a function passed as an argument to another function and is executed once the asynchronous operation completes.
function fetchData(callback) { setTimeout(() => { console.log("Data fetched"); callback(); // Callback executed after async operation }, 2000); } function processData() { console.log("Processing data"); } fetchData(processData);
Explanation:
fetchDatasimulates an async operation (like fetching data) usingsetTimeout.- After 2 seconds,
Data fetchedis logged, and theprocessDatafunction (the callback) is executed, which logsProcessing data.
-
Promises:
Promises provide a cleaner, more powerful way to handle asynchronous code compared to callbacks. A promise represents a value that may be available now, or in the future, or never.
let fetchData = new Promise((resolve, reject) => { setTimeout(() => { let success = true; // Simulating success or failure if (success) { resolve("Data fetched successfully"); } else { reject("Data fetching failed"); } }, 2000); }); fetchData .then((message) => { console.log(message); // Logs if the promise is resolved }) .catch((error) => { console.log(error); // Logs if the promise is rejected });
Explanation:
- A promise is created with two states:
resolvefor success andrejectfor failure. - If the operation succeeds,
resolveis called, and the.then()method logs the success message. - If the operation fails,
rejectis called, and the.catch()method logs the error.
- A promise is created with two states:
-
Async/Await:
async/awaitis a modern approach to handle promises and write cleaner, synchronous-looking asynchronous code.awaitpauses the execution of anasyncfunction until the promise is resolved or rejected.async function fetchData() { try { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("Data fetched"), 2000); }); let result = await promise; // Pauses until the promise is resolved console.log(result); // Logs "Data fetched" } catch (error) { console.log(error); // Handles rejection } } fetchData();
Explanation:
- The
asynckeyword is used to define an asynchronous function. - Inside the function,
awaitwaits for the promise to be resolved and stores the result inresult. - If the promise is rejected, the
catchblock handles the error.
- The
-
Example Breakdown:
-
Callback Example:
function getUserData(callback) { setTimeout(() => { callback("User data fetched"); }, 2000); } getUserData((data) => { console.log(data); // Logs "User data fetched" });
-
Promise Example:
function getUserData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve("User data fetched via Promise"); }, 2000); }); } getUserData().then((data) => { console.log(data); // Logs "User data fetched via Promise" });
-
Async/Await Example:
async function getUserData() { let data = await new Promise((resolve) => { setTimeout(() => resolve("User data fetched via Async/Await"), 2000); }); console.log(data); // Logs "User data fetched via Async/Await" } getUserData();
-
-
Key Difference:
-
Callbacks: They lead to callback hell when nesting multiple asynchronous operations.
-
Promises: They provide better readability, but chaining many
.then()can still make the code hard to follow. -
Async/Await: The most readable and maintainable method to write asynchronous code, but must be used inside
asyncfunctions.
-
-
Best Practice:
- Use
async/awaitfor handling asynchronous code as it makes the code cleaner and easier to read. - Promises are still useful in cases where you need to handle multiple promises simultaneously, like with
Promise.all().
async function fetchAllData() { let promise1 = new Promise((resolve) => setTimeout(() => resolve("Data 1 fetched"), 1000)); let promise2 = new Promise((resolve) => setTimeout(() => resolve("Data 2 fetched"), 2000)); let [data1, data2] = await Promise.all([promise1, promise2]); console.log(data1); // Logs "Data 1 fetched" console.log(data2); // Logs "Data 2 fetched" } fetchAllData();
Explanation:
Promise.all()waits for all promises in the array to resolve and returns their results as an array.- This is particularly useful for fetching multiple resources in parallel.
- Use