javascript-promises
---
title: JavaScript Promises
description: Understanding how to work with Promises in JavaScript for asynchronous operations
---
# JavaScript Promises
## Introduction
In the world of JavaScript, handling asynchronous operations has always been a crucial aspect of programming. When you fetch data from an API, read a file, or wait for a user action, you need a way to manage these operations that don't complete immediately. This is where **Promises** come in.
A Promise in JavaScript is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. Think of it as a "promise" that a result will be returned at some point in the future.
Before we dive deeper, let's understand why Promises were introduced:
- To handle asynchronous operations in a more elegant way than callbacks
- To avoid "callback hell" (deeply nested callbacks)
- To provide better error handling for asynchronous code
- To chain asynchronous operations in a more readable manner
## Basic Promise Syntax
A Promise in JavaScript is created using the `Promise` constructor which takes a function (called the executor) with two parameters: `resolve` and `reject`.
```javascript
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation here
if (/* operation successful */) {
resolve(value); // Success case
} else {
reject(error); // Failure case
}
});
A Promise can be in one of three states:
- Pending: Initial state, neither fulfilled nor rejected
- Fulfilled: The operation completed successfully
- Rejected: The operation failed
Let's create a simple Promise that simulates an API call:
function simulateApiCall(shouldSucceed) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldSucceed) {
resolve("Data fetched successfully!");
} else {
reject("Error: Unable to fetch data!");
}
}, 2000); // Simulating a 2-second delay
});
}
// Usage:
console.log("Starting API call...");
simulateApiCall(true)
.then((data) => {
console.log(data); // Output after 2 seconds: "Data fetched successfully!"
})
.catch((error) => {
console.error(error);
});
console.log("API call initiated. Waiting for response...");
Output:
Starting API call...
API call initiated. Waiting for response...
// After 2 seconds:
Data fetched successfully!
Working with Promises
Consuming Promises
To handle the result of a Promise, we use the .then()
and .catch()
methods:
.then()
- Handles the fulfilled state (success).catch()
- Handles the rejected state (failure).finally()
- Runs code regardless of whether the promise was fulfilled or rejected
myPromise
.then((result) => {
console.log("Success:", result);
})
.catch((error) => {
console.error("Error:", error);
})
.finally(() => {
console.log("Promise operation completed (success or failure)");
});
Chaining Promises
One of the powerful features of Promises is the ability to chain multiple asynchronous operations:
function getUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: userId, name: "John Doe" });
}, 1000);
});
}
function getUserPosts(user) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ id: 1, title: "First Post", userId: user.id },
{ id: 2, title: "Second Post", userId: user.id }
]);
}, 1000);
});
}
// Chain promises
getUserData(123)
.then((user) => {
console.log("User data:", user);
return getUserPosts(user); // Return a new promise
})
.then((posts) => {
console.log("User posts:", posts);
})
.catch((error) => {
console.error("Error:", error);
});
Output:
// After 1 second:
User data: { id: 123, name: 'John Doe' }
// After another second:
User posts: [ { id: 1, title: 'First Post', userId: 123 }, { id: 2, title: 'Second Post', userId: 123 } ]
Error Handling in Promises
Error handling is a crucial aspect of working with Promises. There are various ways to handle errors:
Using .catch()
getUserData(-1) // Invalid ID
.then((user) => {
console.log("User data:", user);
return getUserPosts(user);
})
.then((posts) => {
console.log("User posts:", posts);
})
.catch((error) => {
console.error("Something went wrong:", error);
// Handle the error appropriately
});
Error Propagation
Errors in Promises propagate down the chain until they encounter a .catch()
block:
function processStep1() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Step 1 complete"), 500);
});
}
function processStep2(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Simulating an error in step 2
reject("Error in step 2");
}, 500);
});
}
function processStep3(data) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("Step 3 complete: " + data), 500);
});
}
processStep1()
.then(processStep2)
.then(processStep3)
.then((result) => console.log("Final result:", result))
.catch((error) => console.error("Error caught:", error));
Output:
// After about 1 second:
Error caught: Error in step 2
Promise Methods
JavaScript provides several static methods for working with multiple promises:
Promise.all()
Waits for all promises to be resolved, or for any to be rejected:
const promise1 = Promise.resolve("Hello");
const promise2 = new Promise((resolve) => setTimeout(resolve, 2000, "World"));
const promise3 = fetch("https://api.example.com/data").then(res => res.json());
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // Array containing results of all promises
})
.catch((error) => {
console.error("At least one promise rejected:", error);
});
Promise.race()
Returns as soon as any of the promises resolves or rejects:
const promise1 = new Promise((resolve) => setTimeout(resolve, 2000, "Promise 1 won"));
const promise2 = new Promise((resolve) => setTimeout(resolve, 1000, "Promise 2 won"));
Promise.race([promise1, promise2])
.then((value) => {
console.log(value); // Output: "Promise 2 won" (after 1 second)
});
Promise.allSettled()
Waits until all promises have settled (each may fulfill or reject):
const promises = [
Promise.resolve(1),
Promise.reject("Error occurred"),
new Promise(resolve => setTimeout(resolve, 100, "Delayed result"))
];
Promise.allSettled(promises)
.then((results) => {
console.log(results);
// Results will be an array of objects with status and value/reason
});
Output:
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'Error occurred' },
{ status: 'fulfilled', value: 'Delayed result' }
]
Promise.any()
Returns the first promise that fulfills, or all rejections if all promises reject:
const promises = [
new Promise((resolve, reject) => setTimeout(reject, 1000, 'Error 1')),
new Promise((resolve, reject) => setTimeout(resolve, 2000, 'Success 1')),
new Promise((resolve, reject) => setTimeout(resolve, 1500, 'Success 2'))
];
Promise.any(promises)
.then(value => console.log(value)) // Output after 1.5 seconds: "Success 2"
.catch(error => console.error(error));
Real-world Examples
Example 1: Fetching Data from an API
function fetchUserData(userId) {
return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
});
}
function fetchUserPosts(userId) {
return fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
});
}
// Usage
fetchUserData(1)
.then(user => {
console.log("User details:", user);
return fetchUserPosts(user.id);
})
.then(posts => {
console.log(`User has ${posts.length} posts.`);
console.log("First post:", posts[0]);
})
.catch(error => {
console.error("Failed to fetch data:", error);
});
Example 2: Loading Images with Promises
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(new Error(`Failed to load image from ${url}`));
};
img.src = url;
});
}
// Usage
const imageUrl = "https://example.com/image.jpg";
loadImage(imageUrl)
.then(img => {
console.log("Image loaded successfully!");
console.log(`Width: ${img.width}, Height: ${img.height}`);
document.body.appendChild(img);
})
.catch(error => {
console.error(error.message);
// Show a fallback image or display an error message
});
Common Pitfalls and Best Practices
Avoid Common Mistakes
- Forgetting to return a Promise in chain:
// Incorrect
getData()
.then(data => {
processData(data); // Returns a promise but it's not returned from .then()
})
.then(processedData => {
// processedData will be undefined!
});
// Correct
getData()
.then(data => {
return processData(data); // Return the promise
})
.then(processedData => {
// Now processedData contains the processed data
});
// Or more concisely
getData()
.then(data => processData(data))
.then(processedData => {
// processedData contains the processed data
});
- Not handling errors properly:
// Incorrect - errors will be silently ignored
fetchData()
.then(data => processData(data))
.then(result => displayResult(result));
// Correct - always add error handling
fetchData()
.then(data => processData(data))
.then(result => displayResult(result))
.catch(error => handleError(error));
Best Practices
- Always return values from then() handlers
- Always add a catch() at the end of your promise chain
- Use finally() for cleanup operations
- Keep your promises as simple as possible
- Avoid nesting promises (use chaining instead)
Summary
Promises are a powerful tool for handling asynchronous operations in JavaScript. They provide a clean and elegant way to work with operations that take time to complete, such as API calls, file operations, and timeouts.
Key points to remember:
- Promises represent the eventual completion or failure of an asynchronous operation
- They have three states: pending, fulfilled, or rejected
- Use
.then()
to handle successful outcomes and.catch()
for errors - Promises can be chained to avoid callback hell and make code more readable
- Use Promise methods like
Promise.all()
,Promise.race()
,Promise.allSettled()
, andPromise.any()
to handle multiple promises - Always handle errors in your promise chains with
.catch()
Additional Resources
For further learning:
Exercises
-
Create a Promise that resolves after a random time between 1-5 seconds. Log the time it took to resolve.
-
Implement a function called
retry
that attempts to execute a promise-returning function multiple times until it succeeds or reaches a maximum number of attempts. -
Create a function that loads three images in parallel and displays them on a webpage. Use
Promise.all()
to wait for all images to load. -
Implement a timeout mechanism for a fetch operation: if the fetch doesn't complete within 5 seconds, reject the promise.
-
Create a real-world example of chaining at least 3 promises to complete a multi-step operation (like fetching user data, then user's friends, then details about those friends).
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)