Asynchronous programming is essential for building fast, efficient, and responsive JavaScript applications — especially when dealing with operations like fetching data from APIs, reading files, or waiting for timers. This guide explores two powerful concepts that help manage asynchronous code elegantly: Promises and Async/Await.
A Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value.
- Pending: The operation is ongoing.
- Fulfilled: The operation completed successfully.
- Rejected: The operation failed with an error.
Promises let you write asynchronous code that is easier to read and manage than traditional callbacks.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // or false to simulate failure
if (success) {
resolve("Operation successful!");
} else {
reject("Operation failed!");
}
}, 1000);
});
myPromise
.then(result => console.log(result)) // Handle success
.catch(error => console.error(error)); // Handle error
- Async/Await is syntactic sugar built on top of Promises that makes asynchronous code look and behave more like synchronous code, improving readability and maintainability.
- async keyword declares an asynchronous function that always returns a Promise.
- await pauses the function execution until the awaited Promise settles (fulfilled or rejected).
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Data fetched successfully!");
}, 1000);
});
};
async function getData() {
try {
const result = await fetchData(); // Wait for promise to resolve
console.log(result);
} catch (error) {
console.error("Error:", error);
}
}
getData();
- Improved readability: Avoids deeply nested callbacks ("callback hell").
- Better error handling: Use catch or try-catch blocks to manage errors cleanly.
- Sequential & parallel control: Easily chain or run multiple async tasks.