JavaScript Promises Tutorial - Section 2: Fundamentals
.then() method.catch().finally().then() is used to handle a fulfilled Promise:
promise.then(onFulfilled, onRejected);
const promise = Promise.resolve('Hello');
promise.then(value => {
console.log(value); // 'Hello'
});
const promise1 = Promise.resolve(1);
const promise2 = promise1.then(x => x + 1);
const promise3 = promise2.then(x => x + 1);
promise3.then(result => console.log(result)); // 3
The next .then() receives that value:
Promise.resolve(5)
.then(x => x * 2) // Returns 10
.then(x => x + 3) // Returns 13
.then(x => console.log(x)); // 13
The next .then() waits for that Promise:
Promise.resolve(1)
.then(x => {
return new Promise(resolve => {
setTimeout(() => resolve(x * 2), 1000);
});
})
.then(x => console.log(x)); // 2 (after 1 second)
Promise.resolve(5)
.then(x => {
console.log(x); // 5
// No return statement
})
.then(x => console.log(x)); // undefined
.catch() handles rejected Promises:
promise.catch(onRejected);
// Equivalent to:
promise.then(null, onRejected);
Promise.reject(new Error('Failed'))
.catch(error => {
console.error('Caught:', error.message);
});
fetchUser(1)
.then(user => fetchOrders(user.id))
.then(orders => processOrders(orders))
.catch(error => {
console.error('Error anywhere in chain:', error);
});
Important: .catch() catches errors from any previous step in the chain.
Errors propagate down the chain until caught:
Promise.resolve(1)
.then(x => {
throw new Error('Oops!');
})
.then(x => {
console.log('This will not run');
})
.then(x => {
console.log('This will not run either');
})
.catch(error => {
console.error('Caught here:', error.message);
});
.finally() runs regardless of success or failure:
promise.finally(onFinally);
Perfect for cleanup operations:
showLoadingSpinner();
fetchData()
.then(data => displayData(data))
.catch(error => showError(error))
.finally(() => {
hideLoadingSpinner(); // Always runs
});
.finally() callback receives no argumentsPromise.resolve('value')
.finally(() => {
console.log('Cleanup');
// Cannot access 'value' here
})
.then(value => {
console.log(value); // 'value' - passed through
});
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.catch(error => console.error(error))
.finally(() => cleanup());
.catch() can return a value to recover:
fetchPrimaryAPI()
.catch(error => {
console.log('Primary failed, trying backup');
return fetchBackupAPI();
})
.then(data => {
console.log('Got data:', data);
});
let isLoading = false;
function fetchUserData(userId) {
isLoading = true;
return fetch(`/api/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('User data:', data);
return data;
})
.catch(error => {
console.error('Failed to fetch user:', error);
throw error; // Re-throw to propagate
})
.finally(() => {
isLoading = false;
});
}
// Bad - breaks the chain
promise
.then(result => {
doSomething(result); // Not returned!
})
.then(result => {
console.log(result); // undefined
});
// Good
promise
.then(result => {
return doSomething(result);
})
.then(result => {
console.log(result); // Correct value
});
// Bad - callback hell with Promises
promise.then(result => {
doSomething(result).then(newResult => {
doMore(newResult).then(finalResult => {
console.log(finalResult);
});
});
});
// Good - flat chain
promise
.then(result => doSomething(result))
.then(newResult => doMore(newResult))
.then(finalResult => console.log(finalResult));
// Bad - unhandled rejection
promise.then(result => {
console.log(result);
});
// Good - always handle errors
promise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
});
.then() handles fulfilled Promises and returns a new Promise.catch() handles rejected Promises from any previous step.finally() runs cleanup code regardless of outcome.then() to maintain the chain.catch()Next, we'll learn about Promise.all() and how to run multiple Promises in parallel!