Last week, we learned how to wrap a function in a Promise in vanilla JS. Today, we'll learn how to consume promises using the .then()
, .catch()
, and .finally()
methods.
Let's revisit our getData()
function from last week. It mocks an API call and returns a Promise
with the result. The promise may be either resolved or rejected:
function getData() {
function fiftyFifty() {
return Math.round(Math.random());
}
function executor(resolutionFunc, rejectionFunc) {
setTimeout(function () {
// If there's an "error", reject the Promise with it
if (!fiftyFifty()) {
rejectionFunc(new Error("500 Interval Server Error"));
return;
}
// Otherwise, resolve the Promise with the "API data"
resolutionFunc([
{ id: 1, name: "SpongeBob" },
{ id: 2, name: "Patrick" },
{ id: 3, name: "Squidward" },
]);
}, 3000);
}
return new Promise(executor);
}
The .then()
method
To consume the Promise
returned by our getData()
function, we can chain a call to the .then()
method. The first argument is a callback function for a resolved promise. The second argument is a callback function for a rejected promise:
// Call console.log() on resolve; call console.error() on reject
getData().then(console.log, console.error);
The cool thing about the .then()
method is that we can keep chaining it. If we need to run multiple asynchronous operations in a sequence—passing the preceding result into the next operation—we can do something like this:
getData().then(anotherAsyncOp).then(console.log);
The value we return from the preceding callback function gets passed into the next callback function. In the example above, that means the value we return from the anotherAsyncOp
function gets passed into the console.log
method.
The .catch()
method
The second parameter of the .then()
method—the callback function for a rejected promise—is optional. Notice that in our previous example, we didn't provide a second argument to either of our calls to the .then()
method:
getData().then(anotherAsyncOp).then(console.log);
The alternative way to handle rejected promises is to call the .catch()
method. Unlike the .then()
method, the .catch()
method only deals with rejected promises. The .catch()
method behaves the same way as if we pass undefined
to the first parameter of the .then()
method:
// Only handle rejected promises
getData().then(undefined, console.error);
The useful thing about the .catch()
method is that we can use it as a catch-all for any error that precedes it:
// Call console.error() for errors and rejected promises
getData()
.then(anotherAsyncOp)
.then(console.log)
.catch(console.error);
In the example above, if either the anotherAsyncOp
function or the console.log
method throws an error, the .catch()
method will catch it and pass it into the console.error
method.
The .finally()
method
The .finally()
method lets us run some code once our Promise
is settled. A promise is settled if it's either fulfilled or rejected, but not pending. The .finally()
method doesn't pass a value into the callback function we provide. It's useful for avoiding duplication in our .then()
and .catch()
callbacks.
Imagine we want to print a message once our Promise
is settled. We could call the same onSettled()
function inside our .then()
and .catch()
callbacks:
function onFulfilled(value) {
console.log(value);
onSettled();
}
function onRejected(reason) {
console.error(reason);
onSettled();
}
function onSettled() {
console.info("The Promise returned by getData() is now settled.");
}
getData().then(onFulfilled).catch(onRejected);
But we can just use our onSettled()
function as the callback for the .finally()
method instead:
function onSettled() {
console.info("The Promise returned by getData() is now settled.");
}
getData()
.then(console.log)
.catch(console.error)
.finally(onSettled);
Async functions
ES2017 (ES8) introduced the async
and await
keywords. Next week, we'll learn how we can use them to write asynchronous code that feels more synchronous.