Photo by JJ Ying on Unsplash

It’s time, friend.

Maybe you’re new to web and trying to pick up the latest front-end tech, or maybe you’re a season web developer, but just have been putting off fully understanding promises. async/await is a fresh take on asynchronous JavaScript, but it’s built upon promises, so it’s well worth understanding promises before getting into async/await .

Promises truly aren’t complicated but it can be hard breaking into them. This is because promises are their own type of design pattern — they may be a new paradigm to you in the same way that functional React components can seem foreign to those coming from an object-oriented background.

Luckily for you, my growing pains of learning JavaScript promises are still fresh in my memory and hopefully I can help you get through yours as fast as possible!

Quick question though — did you come here looking for async/await ? If you did, here’s a companion article on async/await !

Without further ado, Promises!

What The Heck Is A Promise

Semantically, a Promise represents an asynchronous task that will be done but potentially isn’t yet done. Personally, I understand promises a little better after getting a sense of what the code looks like.

Here’s a snippet of a bare-bones promise.

An empty promise.

A Promise is a JavaScript class, and its constructor takes in a single argument: a function called the executor function. The executor function itself has two arguments called resolve and reject . The code inside the executor function runs and you call resolve() when you’re done and reject() if something goes wrong.

Here’s the same snippet with the executor function filled out:

A promise with its executor function filled out, representing a coin flip.

The Promise in this snippet represents a coin flip that takes some time to complete. You can see that in this snippet, the executor function now has a body with some code for flipping a coin.

A coin is flipped via flip() which we assume to take a few seconds If the flip succeeded, call resolve() to say that the Promise is complete If something went wrong and flipResult is undefined , call reject() to say that there was an error

You now have a basic Promise !

When I got to this point, I had one burning question and couldn’t find a definitive answer: when does the code inside the executor function actually run? And the answer is right away! There are ways to have a Promise start a later time — I’ll cover that later.

Interacting With Surrounding Code

Now that we’ve got a Promise running, let’s see how it interacts with the context around it. One of the most important facets of using a Promise is that even though you’ve created a Promise , the code around it keeps running!

Here’s an example:

What do you think the order of these console.log statements will be? The answer is this:

“I’m about to flip a coin!” “I’m flipping the coin!” “I have flipped the coin.” “Here is the coin flip result! Heads”

If you’re new to promises, you may have thought that the snippet looks as if “I have flipped the coin.” should definitely come last. Now you know though! As long as your executor function contains asynchronous code, the surrounding code keeps running outside the Promise instead of waiting for the asynchronous work to complete.

So how do we wait until the Promise has actually resolved?

Waiting For A Promise To Finish

You can call several functions on a Promise in order to run code in response to the Promise completing. The first one we’ll discuss is .then() :

.then() is a function of a Promise that takes in a function that will be run after the code inside the executor function of the Promise calls resolve() . In this new snippet, the following will be the order of logs:

“I’m about to flip a coin!” “I’m flipping the coin!” “Here is the coin flip result! Heads” “I have flipped the coin.”

The content of the .then() function is only called after the resolve() call, which only happens after “Here is the coin flip result! Heads” has been printed to the log. We’ve now successfully waited for the Promise to complete before doing something!

Here are all of the other Promise functions that you can use:

Why Promises? Why Not Callbacks?

You have may heard of a phenomenon called “callback hell”. Once you have enough code depending on the asynchronous result of other code, you get something that looks like the following:

Maybe it doesn’t look too bad but it’s very much error prone, difficult to maintain, and has poor readability.

Returning Values Out Of Promises

Waiting for a Promise is useful, but it’s even more useful to be able to tell surrounding code what the resolved value of the Promise is. To accomplish this, you need two things:

resolve() should take in an argument The function in .then() should take in a parameter

Using A Promise Later

One thing that really confused me in the beginning was that I thought you had to add all of your .then() and .catch() calls when you create the Promise — but that’s not true! Here’s an example:

Once you create a Promise and store that in a variable, you can continue to append .then() and .catch() calls to it later on even though the code inside finished running a long time ago.

Chaining Promises

The most aesthetic property of promises is that they can be chained:

This is highly maintainable and readable because as you continue to add more things to depend on, you can simply keep adding chained .then() calls.

But there’s something tricky here that you need to know! Look at the snippet above. You might think that doSomethingElse() will wait for doSomething() to complete. But it doesn’t! Here’s why.

Every .then() call in a chain waits on the last Promise in the chain, not the .then() before it. That means if doSomething() takes a while, doSomethingElse() may finish executing before doSomething() is finished executing.

Here’s a demo on Codepen. Check out the JavaScript view to check out the code!

There’s an easy way to get around this though. Since the general rule with chains of .then() , .catch() , and .finally() calls are that they wait on the last Promise returned in the chain, you can do something like this to make each .then() wait on the last one:

By returning a Promise in each .then() block, then following .then() will wait on it instead of the original Promise .

Making A Promise Start Later

Sometimes you want to be able to create a Promise but not actually run the code just yet. This is a perfectly valid use case, especially for helper functions.

By wrapping a Promise in a function, you effectively postpone the running of the Promise executor until the function is actually called. Here’s an example:

The above snippet will print the logs to console in the following order:

“After promise-creating function” “Done!”

Using functions that return a Promise allow you to define a Promise in a helper function but not actually run the code until some other time.

You can also use this pattern to parameterize your promises as if it’s a function:

Static Helpers

Finally, the Promise class comes with a few handy static functions. The following gist explains how they work, and I’ll use them in the cheat sheet later on.

Of these static functions, I find myself most often making use of Promise.all() and Promise.allSettled() — they’re especially useful for firing off a collection of API requests and only continuing when they’re all done. A good example of that kind of scenario would be creating 10 objects via an API, then once those are done, using those 10 objects in some other way via an API, then once those are done, showing a success message to the user.

Note: Promise.allSettled() has low browser support at the time this is written!

Codepen Cheat Sheet

I’ve created executable Codepens demonstrating how to use each of techniques shown above. Please play around with them and fork them and use them as references for your own projects!

Then, Catch, And Finally

Passing Values Through Chains

Waiting For Promises Mid-Chain

Managing Multiple Promises

Data Fetching Example

React Example

Async/Await