What Is a JavaScript Promise?

Dillon Luther
The Startup
Published in
5 min readJan 19, 2021
Photo by Womanizer WOW Tech on Unsplash

I found myself typing this question into browsers far more than I would like to admit, so I finally decided to write about it in an attempt ingrain it in my head. This isn’t an exhaustive deep dive on Promises, but rather a higher-level overview of the important concepts and some info about using them with async and await. Hopefully, you find this information helpful too. I’ve included references at the bottom if you would like to dive deeper.

So what is a Promise?

A Promise is an object that should produce a value in the future. It is similar to a promise in real life. Say I ask a friend to go to lunch tomorrow. They tell me they will check their calendar and they promise to give me an answer by the end of the day. As the end of the day rolls around, one of three things can happen: they can tell me that yes, they can do lunch; they can say no, they can’t do lunch; or they can not respond to me at all, and I’ll be left sadly wondering until I hear from them… A JavaScript Promise works similarly, as it always has one of three potential states. The three states a Promise can have are:

  1. pending: The state of the promise from its declaration until the state changes to fulfilled or rejected (i.e., me anxiously waiting for my friend to let me know about lunch tomorrow)
  2. fulfilled: The state of the promise if resolve() is called (i.e., my friend responding saying we’re on for lunch)
  3. rejected: The state of the promise if reject() is called (i.e., my friend responding saying they can’t do lunch)

Now let’s take a look at how to make a promise:

An empty promise

A promise takes a special function as its argument, the executor. The first argument of the executor function is a variable for the builtin resolve() function, and the second is a variable for the builtin reject() function. resolve() and reject() are the functions that settle the Promise, or transition its state from “pending”. resolve() transitions the state to fulfilled, and reject() transitions the state to rejected.

  • Note: often we name the functions “resolve” and “reject”, but we can call them anything we want to: new Promise((a, b) => ..., where a() and b() would act as resolve() and reject(), respectively.

Wait, show me how these states actually work?

Try it on repl.it
  • Notice that we only passed a single variable to the executor function because I knew I was only handling the resolve(). We will usually want to pass two arguments because reject() throws an error, and we will almost certainly want to handle this error rather than swallowing it quietly.

So what’s so special about these promises?

The real value in a promise is that they allow us to write asynchronous code as if we were writing synchronous code (we could do this before with callback hell, but Promises are much nicer to handle). We also don’t have to wait for a Promise to to settle to continue onto next lines of code. When the Promise is declared, it can begin its asynchronous work, and then the rest of the code goes on as normal. Then when the Promise is settled, the part of the code that handles the resolved/rejected Promise will continue.

Try it on repl.it

.then() and .catch()

When a Promise settles, we can handle it using the then() function, as shown in the pickAColor() example above. After pickAColor('blue') settles (in this case, it resolved), then() will execute the callback passed to it. In our example above, result is the resolved value from pickAColor('Blue'). What happens if the promise was instead rejected? We can pass a second callback to then() to handle the error from the rejection.

Try it on repl.it

Since then() returns another Promise, we can keep calling then() on a result as many times as we return a promise. However, right how this means we have to write lots of callbacks for error handling, which is tedious and not very DRY:

We wrote the same error callback for each .then(). Thankfully, someone smarter than us helped build Promises. Instead of handling each error for each then(), we can use a catch() statement after a series of then() statements to handle any error that falls through (it acts like the catch statements that we’re familiar with in try / catch clauses):

Try it on repl.it
  • catch() will catch handle an error in any of then clauses before it.

Async and Await

Using Promises as we have above was a big improvement from callback hell in the prior age. Now, we have an even cleaner implementation of promises via async and await.

Async

Using the keyword async on a function means the function will always return a Promise. Even if I just return a value, it will wrap it in a resolved promise automatically:

Try it on repl.it

Await

The await keyword can only be used inside async functions. await tells JS to wait for a Promise to settle, and it will actually pause the code until this happens. Then it can be used synchronously in code that calls it later.

Try it on repl.it
  • I’ll say it again because it surprised me. await will pause the execution of an async function until the Promise settles. Hence, if we have something that does not depend on the results of the promise, we SHOULD NOT include it in the async function or it will be delayed.

You might be wondering how these things appear in the real world (as I often do when I read code examples), so I wanted to include an example with an API. Pick any api you want, preferably that return JSON if you want to follow along below. There are a bunch of free APIs at https://apilist.fun/, and I picked https://dog.ceo/dog-api/. Finally, we can use use the asynchronous fetch() call with our async and await to handle promises:

Try it in CodePen

Thank you so much for reading this! If you found this useful at all, or if I made any errors or something could be said in a better way, please feel free to share your feedback! As I mentioned, this is NOT meant to be an exhaustive article about Promises. There is definitely more to explore, and I hope this is a good jumping-off point for you.

Finally, I’ve included a list of resources that were helpful to me in exploring this, and many of them dive much deeper. Enjoy!

Resources

--

--

Dillon Luther
The Startup

Mechanical engineer turned software engineer and west coast swing dancer.