Promises and async/await
Promises and async/await are essential for handling asynchronous operations in JavaScript. Whether you're fetching data from an API, reading files, or working with timers, these patterns help you write cleaner, more readable code. In React, they are used for data fetching, handling side effects, and working with hooks like useEffect.
A Promise is an object representing the eventual completion (or failure) of an asynchronous operation. It has three states:
- Pending – initial state, neither fulfilled nor rejected.
- Fulfilled – operation completed successfully.
- Rejected – operation failed.
Creating a Promise
const myPromise = new Promise((resolve, reject) => {
// Do some async work
const success = true;
if (success) {
resolve('Operation successful!');
} else {
reject('Operation failed!');
}
});
Consuming a Promise – .then() and .catch()
myPromise
.then(result => console.log(result)) // 'Operation successful!'
.catch(error => console.error(error))
.finally(() => console.log('Done')); // Always runs
Key Insight: Promises allow you to write asynchronous code that doesn't rely on nested callbacks (callback hell).
You can chain .then() calls to perform sequential asynchronous operations.
js
fetchUserData(userId)
.then(user => fetchUserPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.error('Something went wrong:', error));
Each .then() returns a new promise, allowing you to transform data along the way.
Promise.all()
Waits for all promises to resolve. If any rejects, it rejects immediately.
``` js const promise1 = fetch('/api/user1'); const promise2 = fetch('/api/user2'); const promise3 = fetch('/api/user3');
Promise.all([promise1, promise2, promise3]) .then(responses => console.log(responses)) .catch(error => console.error('One request failed:', error)); ```
Promise.race()
Returns the result of the first promise that settles (resolves or rejects).
js
Promise.race([
fetch('/api/data'),
new Promise((_, reject) => setTimeout(() => reject('Timeout'), 5000))
])
.then(data => console.log(data))
.catch(error => console.error(error));
React Use Case
Promise.all is useful for fetching data from multiple endpoints simultaneously before rendering a page.
async/await is a cleaner syntax built on top of Promises. It makes asynchronous code look synchronous.
async Function
An async function always returns a Promise.
async function getUserData(userId) {
return `User ${userId}`;
}
// The above is equivalent to:
function getUserData(userId) {
return Promise.resolve(`User ${userId}`);
}
await Keyword
await pauses the execution of the async function until the Promise is resolved.
async function getPost() {
try {
const response = await fetch('/api/post');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching post:', error);
}
}
Important: await can only be used inside async functions (or at the top level in modern modules).
Use try/catch to handle errors when using await.
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error.message);
// Return a fallback value or re-throw
return null;
}
}
You can also chain .catch() if you prefer:
async function fetchData() {
const response = await fetch('https://api.example.com/data').catch(err => {
console.error('Network error:', err);
return null;
});
if (!response) return;
// process response...
}
In React, useEffect is the most common place for data fetching with async/await.
Basic Pattern
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchUsers();
}, []); // Empty dependency array = run once
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
Using Custom Hooks
You can extract the fetch logic into a custom hook for reusability.
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchData() {
try {
const response = await fetch(url, { signal: abortController.signal });
if (!response.ok) throw new Error('Fetch failed');
const json = await response.json();
setData(json);
} catch (err) {
if (err.name !== 'AbortError') setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
return () => abortController.abort(); // Cleanup
}, [url]);
return { data, loading, error };
}
Best Practice: Always handle cleanup (aborting fetch) in useEffect to avoid memory leaks.
Event Handlers with async/await
function SubmitButton() {
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(formData)
});
if (response.ok) {
// Handle success
}
} catch (error) {
// Handle error
}
};
return <button onClick={handleSubmit}>Submit</button>;
}
- Promises – represent asynchronous operations with
.then(),.catch(), and.finally(). - Promise.all() – waits for all promises to resolve; Promise.race() – resolves with the first settled promise.
- async/await – syntactic sugar that makes Promises easier to read and write.
- Error handling – use
try/catchwith async/await, or.catch()with Promises. - In React, use
useEffectfor data fetching, handle cleanup, and manage loading/error states.
Key Takeaway
"Promises and async/await turn asynchronous chaos into readable, maintainable code. They are essential for any modern React application that talks to a server."
Happy coding!