The last few months I have been learning JavaScript and React.js while finishing the final stretch of the Software Engineering curriculum at Flatiron School. Using the Fetch API has been integral part of this experience. While the curriculum does explain how to use fetch() and promises, to some detail, I realized that there was a lot to the Fetch API and promises that I still didn’t understand. I wanted to understand the tools I had been using a little better. This blog will be about promises, and I will make a future blog post about the Fetch API.

Asynchronous Operations

JavaScript is single threaded. It can only do one thing at a time. In a synchronous operation, each part of the code waits until the first part is complete to run. In an asynchronous operation, you can write code that will be executed after the rest of the code has finished running. This code in example:

Will console.log 1, 2, then 3. This is because even though asyncLog() is invoked first, setTimeout() is asynchronous, it will only add ()=>{console.log('3') to the call stack when it is empty.

(Spoiler: fetch() is asynchronous, too!)

Promises

What is a Promise? From MDN:

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

A promise has 3 states: pending, fulfilled, and rejected. The promise represents a value that is not known yet. At first the state of the promise is pending, then the promise will either be fulfilled/resolved or rejected, at which point the promise is considered settled. There are a few things to note here:

  • Once a promise is settled (either fulfilled or rejected) it cannot transition into another state. It is immutable.
  • A settled promise must return a value, even if it is undefined.
  • A promise is thenable.

What does that last bullet point mean? A promise supplies a .then() method. .then() Allows us to attach callbacks to the returned promise object, rather than having to pass them in, it also returns a promise, so it is chainable. You also can (and it is highly recommended to) chain a .catch() for handing errors and rejections. The callback(s) supplied to .then() will only run after the completion of the first asynchronous operation. This predictability is one of the biggest benefits to using promises to handle asynchronous operations.

Using Promises

So we’ve gone over some things about promises, let’s see some code to construct our own. Pay attention to where we place the console.log's.

Now we have some functions that return promises defined, let’s do some chaining!

As you can see from the last two examples if you supply a rejection callback, and use another .then() the return value from the rejection callback will be what gets passed in, and the .catch() will not run. If you don’t supply a rejection callback, it will skip any consecutive .then()’s and look for a .catch(). It is also worth noting that you can supply a .then() after .catch() that will run whether or not any of the promises are fulfilled or rejected. In the second .then() we are supplying the resolve callback inline, which means that it is still returning a promise, we could continue to chain .then()'s, the return value of that inline callback will be what is passed in. You can see from the order of the console.log's that Promises are asynchronous, the code outside of the .then() chain will be run first, the code in each consecutive .then() will wait on the first to be settled before running. Again, this is important because it makes asynchronous code predictable. You know your code won’t be run until the promise is settled. There is another instance method that can be run in the promise chain called .finally(). This method returns a promise, it will be run when the promise is settled, whether it is fulfilled or rejected. It’s purpose is mainly eliminating duplicate code in .then() and .catch().

Promise Static Methods

Promises have a handful of static methods as well. They are:

Promise.all() takes in an array of promises, it waits for them all to be settled. It returns with either an array of all of the values from the resolved promises, or the reason of the first promise that was rejected.

Promise.allSettled() also takes an array of promises and waits for them all to be settled. Unlike Promise.all() it returns a promise with the value of an array of objects with that describe the outcome of each promise.

Promise.any() takes an array of promises, returns a promise with the value from the promise that was fulfilled first. If no promise was fulfilled it returns an error that all promises were rejected.

Promise.race() takes an array of promises. Returns a new promise with either the value or reason for the first promise that was fulfilled or rejected.

Promise.reject() creates a new promise that is rejected with the reason supplied as the argument.

Promise.resolve() creates a new promise that is resolved with the value supplied as the argument. Useful if you need to work with a promise but are unsure whether or not the value you have is one.

Let’s Resolve This

Promises totally changed the way asynchronous code was handled in JavaScript. They allow asynchronous code to be more predictable, easier to write, and understand, and make it easy to chain asynchronous operations. Promises are an essential building block of the Fetch API, and understanding them is crucial to glean any deeper understanding of .fetch(). Hopefully after reading this you have a better understanding of promises and their usefulness.

Written by

Aspiring Software Engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store