Introduction to Promises
Imagine you're at a restaurant. When you place your order, the waiter gives you a receipt number - this is like a Promise in JavaScript. It's not your food (the actual data) yet, but it's a guarantee that you'll either get your food (success) or an explanation why it couldn't be prepared (failure). Just like you can continue chatting with friends while waiting for your food, JavaScript can continue executing other code while waiting for a Promise to resolve.
Creating Your First Promise
Here's how to create a basic Promise:
const orderFood = new Promise((resolve, reject) => {
// Simulating food preparation that takes 2 seconds
setTimeout(() => {
const foodPrepared = true;
if (foodPrepared) {
resolve("🍔 Your burger is ready!");
} else {
reject("❌ Sorry, we're out of ingredients");
}
}, 2000);
});
// Using the Promise
orderFood
.then(food => console.log(food))
.catch(error => console.log(error));
In this example, the Promise represents a pending food order. After 2 seconds, it either resolves with your food or rejects with an error message.
Promise States and Handling
Every Promise exists in one of three states:
- Pending: Like when your order is being prepared
- Fulfilled: Like when your food arrives successfully
- Rejected: Like when the kitchen runs out of ingredients
Handling Multiple Promises with Promise.all()
Imagine you're ordering a full meal with multiple items. Promise.all() is like waiting for all parts of your meal to be ready before serving:
const orderBurger = new Promise(resolve => setTimeout(() => resolve("🍔"), 2000));
const orderFries = new Promise(resolve => setTimeout(() => resolve("🍟"), 1500));
const orderDrink = new Promise(resolve => setTimeout(() => resolve("🥤"), 1000));
Promise.all([orderBurger, orderFries, orderDrink])
.then(meal => console.log("Your complete meal:", meal))
.catch(error => console.log("There was a problem:", error));
Async/Await: A More Modern Approach
Async/await is like having a personal waiter who handles all the waiting for you. Instead of manually managing Promises, you can write code that looks more sequential:
async function orderMeal() {
try {
const burger = await orderBurger;
console.log("Got the burger:", burger);
const fries = await orderFries;
console.log("Got the fries:", fries);
const drink = await orderDrink;
console.log("Got the drink:", drink);
return [burger, fries, drink];
} catch (error) {
console.log("Something went wrong with the order:", error);
}
}
Working with Fetch
The fetch API is like sending your waiter (browser) to another restaurant (server) to get something. Here's a real-world example of fetching user data:
// Fetching user data from an API
async function getUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const userData = await response.json();
return userData;
} catch (error) {
console.error("Failed to fetch user:", error);
}
}
// Using the fetch function
getUser(123)
.then(user => console.log("User data:", user))
.catch(error => console.log("Error:", error));
Important components of a fetch response:
- response.ok: Boolean indicating if the response was successful (status in range 200-299)
- response.status: The HTTP status code (e.g., 200, 404, 500)
- response.headers: Response headers containing metadata
- response.json(): Method to parse JSON response body (returns a Promise)
Debugging with Fetch
Here's a helpful debug function for testing API endpoints:
async function debugFetch(url, options = {}) {
console.log(`🚀 Sending request to: ${url}`);
console.time('Request Duration');
try {
const response = await fetch(url, options);
console.timeEnd('Request Duration');
console.log('📊 Response Status:', response.status);
console.log('🔍 Response Headers:', Object.fromEntries(response.headers));
const data = await response.json();
console.log('📦 Response Data:', data);
return data;
} catch (error) {
console.error('❌ Error:', error);
throw error;
}
}
// Usage example
debugFetch('https://api.example.com/data')
.then(data => console.log('✅ Success!'))
.catch(error => console.log('❌ Failed!'));
Runtime Environments
The fetch API is available in:
- Modern web browsers (Chrome, Firefox, Safari, Edge)
- Node.js (version 18+)
- Deno
For older Node.js versions, you'll need to use alternatives like 'node-fetch' or 'axios'.
Best Practices and Common Patterns
When working with Promises, follow these guidelines:
- Always handle potential errors using try/catch or .catch()
- Chain Promises appropriately instead of nesting them
- Use Promise.all() for parallel operations
- Consider Promise.race() for timeout implementations
- Remember that async functions always return Promises
Example: Implementing a Timeout
function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timed out')), timeout);
});
return Promise.race([
fetch(url),
timeoutPromise
]);
}
// Usage
fetchWithTimeout('https://api.example.com/data', 3000)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));