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.