JavaScript Promises Tutorial - Section 2: Fundamentals
Promise chaining allows you to execute multiple asynchronous operations in sequence, where each operation starts when the previous one succeeds.
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
Every .then() returns a new Promise, allowing you to chain multiple operations:
const promise1 = fetchUser(1);
const promise2 = promise1.then(user => user.name);
const promise3 = promise2.then(name => name.toUpperCase());
promise3.then(result => console.log(result)); // "JOHN DOE"
The value becomes the resolved value of the returned Promise:
Promise.resolve(5)
.then(x => x * 2) // Returns 10
.then(x => x + 3) // Returns 13
.then(x => x.toString()) // Returns "13"
.then(result => console.log(result)); // "13"
The next .then() waits for that Promise to resolve:
fetchUser(1)
.then(user => {
console.log('Got user:', user.name);
return fetchPosts(user.id); // Returns a Promise
})
.then(posts => {
console.log('Got posts:', posts.length);
return posts;
});
The next .then() receives undefined:
Promise.resolve('data')
.then(data => {
console.log(data);
// No return statement
})
.then(result => {
console.log(result); // undefined
});
function getUserProfile(userId) {
return fetchUser(userId)
.then(user => {
console.log('Fetched user:', user.name);
return fetchPosts(user.id);
})
.then(posts => {
console.log('Fetched posts:', posts.length);
return fetchComments(posts[0].id);
})
.then(comments => {
console.log('Fetched comments:', comments.length);
return comments;
})
.catch(error => {
console.error('Error in chain:', error);
throw error;
});
}
getUserProfile(1)
.then(comments => console.log('Final result:', comments));
// ❌ Lost access to user
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => {
// Can't access user here!
return posts;
});
fetchUser(1)
.then(user => {
return fetchPosts(user.id)
.then(posts => ({ user, posts }));
})
.then(({ user, posts }) => {
console.log(user.name, 'has', posts.length, 'posts');
});
fetchUser(1)
.then(user => {
return Promise.all([
user,
fetchPosts(user.id)
]);
})
.then(([user, posts]) => {
console.log(user.name, 'has', posts.length, 'posts');
});
function processCheckout(userId, cartId) {
let order;
return validateCart(cartId)
.then(cart => {
console.log('Cart validated');
return calculateTotal(cart);
})
.then(total => {
console.log('Total calculated:', total);
return processPayment(userId, total);
})
.then(payment => {
console.log('Payment processed:', payment.id);
return createOrder(userId, cartId, payment.id);
})
.then(createdOrder => {
order = createdOrder;
return sendConfirmationEmail(order);
})
.then(() => {
console.log('Email sent');
return updateInventory(order.items);
})
.then(() => {
console.log('Inventory updated');
return order;
})
.catch(error => {
console.error('Checkout failed:', error);
// Rollback logic here
throw error;
});
}
// ❌ Bad: Nested
promise.then(result => {
doSomething(result).then(newResult => {
doMore(newResult).then(finalResult => {
console.log(finalResult);
});
});
});
// ✅ Good: Flat
promise
.then(result => doSomething(result))
.then(newResult => doMore(newResult))
.then(finalResult => console.log(finalResult));
// ❌ Bad: Forgot to return
promise
.then(result => {
doSomething(result); // Not returned!
})
.then(result => {
console.log(result); // undefined
});
// ✅ Good: Return the Promise
promise
.then(result => {
return doSomething(result);
})
.then(result => {
console.log(result); // Correct value
});
promise
.then(step1)
.then(step2)
.then(step3)
.catch(error => {
// Catches errors from any step
console.error('Error:', error);
});
fetchUser(1)
.then(user => user.name)
.then(name => name.toUpperCase())
.then(upperName => `Hello, ${upperName}!`)
.then(greeting => {
console.log(greeting); // "Hello, JOHN DOE!"
});
function getUserData(userId, includeDetails = false) {
return fetchUser(userId)
.then(user => {
if (includeDetails) {
return fetchUserDetails(user.id)
.then(details => ({ ...user, details }));
}
return user;
})
.then(userData => {
console.log('User data:', userData);
return userData;
});
}
.then() always returns a new Promise.then().then() handlers.catch() at the end to handle all errorsNext, we'll dive deeper into error handling patterns in Promise chains!