JavaScript is a single-threaded, asynchronous language. This can often make managing asynchronous operations (like API calls, timers, or file I/O) a challenge. Enter Promises β a powerful tool introduced in ES6 to handle asynchronous tasks in a cleaner and more manageable way.
In this blog, we'll break down what promises are, how to use them, and walk through code examples using both .then()
/.catch()
and async/await
.
π What is a Promise?
A Promise is an object representing the eventual completion or failure of an asynchronous operation.
A promise has three states:
- Pending: Initial state, neither fulfilled nor rejected.
- Fulfilled: The operation completed successfully.
- Rejected: The operation failed.
β Basic Promise Example
Letβs create a simple promise that simulates fetching data from a server using setTimeout
.
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("β
Data fetched successfully!");
} else {
reject("β Failed to fetch data.");
}
}, 1000);
});
};
fetchData()
.then((response) => {
console.log("Success:", response);
})
.catch((error) => {
console.error("Error:", error);
});
Output:
Success: β
Data fetched successfully!
π Using async
/await
with Promises
The async/await
syntax makes writing and reading asynchronous code easier and more intuitive.
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("β
Data fetched successfully!");
} else {
reject("β Failed to fetch data.");
}
}, 1000);
});
};
const getData = async () => {
try {
const data = await fetchData();
console.log("Result:", data);
} catch (error) {
console.error("Error:", error);
}
};
getData();
π Promise Chaining
You can chain multiple .then()
calls to run a sequence of asynchronous operations:
const stepOne = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("Step 1 completed"), 500);
});
};
const stepTwo = (prev) => {
return new Promise((resolve) => {
setTimeout(() => resolve(`${prev} β Step 2 completed`), 500);
});
};
stepOne()
.then(stepTwo)
.then((result) => {
console.log("Chain Result:", result);
});
π§΅ Running Multiple Promises in Parallel
Use Promise.all()
when you want to run multiple promises in parallel and wait for all of them to complete:
const task = (name, delay) =>
new Promise((resolve) => {
setTimeout(() => resolve(`${name} done`), delay);
});
Promise.all([
task("Task 1", 1000),
task("Task 2", 500),
task("Task 3", 1500),
]).then((results) => {
console.log("All tasks done:", results);
});
Output:
All tasks done: ["Task 1 done", "Task 2 done", "Task 3 done"]
β οΈ Error Handling
Always handle errors with .catch()
or try...catch
when using async/await
:
const riskyOperation = () =>
new Promise((_, reject) => {
setTimeout(() => reject("Something went wrong!"), 1000);
});
riskyOperation()
.then((res) => console.log(res))
.catch((err) => console.error("Caught Error:", err));
π§ Conclusion
Promises make working with asynchronous code in JavaScript much more readable and robust. Whether you're chaining multiple calls or using async/await
, they allow you to write cleaner, more maintainable code.
π Key Takeaways:
- Promises can be in one of three states: pending, fulfilled, or rejected.
- Use
.then()
/.catch()
for chaining orasync/await
for cleaner async code. -
Promise.all()
runs multiple promises in parallel. - Always handle promise rejections gracefully.
Part 2
Handling APIs using JavaScript Promises is one of the most common tasks in web development. Here's a complete breakdown with a blog-style explanation and code examples using:
-
fetch()
with.then()
/.catch()
async/await
- Centralized API handling with reusable Promise-based functions
π‘ How to Handle APIs Using JavaScript Promises
JavaScript Promises allow us to work with asynchronous APIs in a clean, readable way. When we call an API (e.g., using fetch()
), it returns a promise β a placeholder for a future value.
πΉ 1. Using fetch()
with .then()
and .catch()
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => {
if (!response.ok) {
throw new Error("API error: " + response.status);
}
return response.json(); // Convert response to JSON
})
.then((data) => {
console.log("β
API Data:", data);
})
.catch((error) => {
console.error("β Error fetching API:", error.message);
});
β Output:
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati",
"body": "quia et suscipit..."
}
πΉ 2. Using async/await
(Modern & Cleaner)
const getPost = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
if (!response.ok) {
throw new Error("API error: " + response.status);
}
const data = await response.json();
console.log("β
API Data:", data);
} catch (error) {
console.error("β Error fetching API:", error.message);
}
};
getPost();
πΉ 3. Reusable Promise-Based API Function
Letβs build a centralized function to handle GET, POST, PUT, DELETE requests using Promises.
const apiRequest = (url, method = "GET", body = null) => {
return new Promise((resolve, reject) => {
fetch(url, {
method,
headers: {
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : null,
})
.then((response) => {
if (!response.ok) {
throw new Error("API error: " + response.status);
}
return response.json();
})
.then(resolve)
.catch(reject);
});
};
// πΉ GET Example
apiRequest("https://jsonplaceholder.typicode.com/posts/1")
.then((data) => console.log("GET Success:", data))
.catch((err) => console.error("GET Error:", err.message));
// πΉ POST Example
apiRequest("https://jsonplaceholder.typicode.com/posts", "POST", {
title: "New Post",
body: "This is the body",
userId: 1,
})
.then((data) => console.log("POST Success:", data))
.catch((err) => console.error("POST Error:", err.message));
π§ Best Practices for Handling APIs with Promises
- β
Always handle errors using
.catch()
ortry...catch
. - π Use
async/await
for cleaner, more readable async code. - β»οΈ Use reusable functions to reduce code duplication.
- π¦ Check
response.ok
before parsing the response.
π§ͺ Bonus: Parallel API Requests using Promise.all()
const urls = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
];
Promise.all(urls.map((url) => fetch(url).then((res) => res.json())))
.then((responses) => {
console.log("β
All API Responses:", responses);
})
.catch((err) => {
console.error("β One of the APIs failed:", err.message);
});
π Conclusion
Using Promises with fetch()
allows us to make API calls cleanly and handle errors gracefully. With async/await
, the code becomes even easier to understand. Wrapping your API logic in reusable Promise-based functions can save time and reduce bugs.
Top comments (0)