JavaScript Promises and Async/Await Cheatsheet - Beginner to Advanced

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.

const flipACoin = new Promise((resolve, reject) => {    

});

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:

const flipACoin = new Promise((resolve, reject) => {    
  const flipResult = flip(); //let's say flip() takes a few seconds
  
  if(flipResult) {
    resolve();
  } else {
    reject();
  }
});

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.

  1. A coin is flipped via flip() which we assume to take a few seconds
  2. If the flip succeeded, call resolve() to say that the Promise is complete
  3. 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:

console.log("I'm about to flip a coin!");

const flipACoin = new Promise((resolve, reject) => {
  console.log("I'm flipping the coin!");
  
  const flipResult = flip(); //let's say flip() takes a few seconds
  
  if(flipResult) {
    console.log("Here is the coin flip result!", flipResult);
    resolve();
  } else {
    reject();
  }
});

console.log("I have flipped the coin.")

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

  1. “I’m about to flip a coin!”
  2. “I’m flipping the coin!”
  3. “I have flipped the coin.”
  4. “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() :

console.log("I'm about to flip a coin!");

const flipACoin = new Promise((resolve, reject) => {
  console.log("I'm flipping the coin!");
  
  const flipResult = flip(); //let's say flip() takes a few seconds
  
  if(flipResult) {
    console.log("Here is the coin flip result!", flipResult);
    resolve();
  } else {
    reject();
  }
}).then(() => {
  console.log("I have flipped the coin.")
});

.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:

  1. “I’m about to flip a coin!”
  2. “I’m flipping the coin!”
  3. “Here is the coin flip result! Heads”
  4. “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:

const flipACoin = new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  //use `.then()` to do something after `resolve()` has been called
}).catch(() => {
  //use `.catch()` to do something after `reject()` has been called
}).finally(() => {
  //use `.finally()` to do something either way
});

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:

callUserApiEndpoint(userId, (data) => {
  const authorId = data.authorId;
  
  callPostsEndpoint(authorId, (data) => {
    const postsByUser = data.posts;
    const topPost = postsByUser[0];
    
    callCommentsEndpoint(topPost.id, (data) => {
      const comments = data.comments;
      const topComment = comments[0];
      
      callResponsesEndpoint(topComment.id, (data) => {
        const responses = data.responses;
        //...
      });
    });
  })
});

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:

  1. resolve() should take in an argument
  2. The function in .then() should take in a parameter
const flipACoin = new Promise((resolve, reject) => {
  const flipResult = flip(); //let's say flip() takes a few seconds
  
  if(flipResult) {
    resolve(flipResult);
  } else {
    reject();
  }
}).then((flipResult) => {
  console.log(`The result was ${flipResult}`);
});

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:

const flipACoin = new Promise((resolve, reject) => {
  const flipResult = flip(); //let's say flip() takes a few seconds
  
  if(flipResult) {
    resolve(flipResult);
  } else {
    reject();
  }
});

//...
//do other things
//...

console.log("Wait did I flip the coin?");

flipACoin.then((flipResult) => {
  console.log("Oh I did!");
});

console.log("Double checking...");

flipACoin.then((flipResult) => {
  console.log("Okay I definitely did!");
});

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:

const flipACoin = new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  doSomething();
}).then(() => {
  doSomethingElse();
}).then(() => {
  doAnotherThing();
});

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:

const doThisLater = () => {
  return new Promise((resolve, reject) => {
    console.log("Done!");
    resolve();
  });
};

console.log("After promise-creating function");

doThisLater();

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

  1. “After promise-creating function”
  2. “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:

const timeout = (millisecondsToWait, onTimeoutComplete) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      onTimeoutComplete();
      resolve();
    }, millisecondsToWait)
  });
};

timeout(3000, () => {
  console.log("Timeout finished!");
}).then(() => {
  console.log("Promise resolved!");
});

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.

const promise1 = new Promise((resolve, reject) => resolve("Resolved promise1!"));
const promise2 = new Promise((resolve, reject) => reject("Rejected promise2!"));
const promise3 = new Promise((resolve, reject) => resolve("Resolved promise3!"));
const allPromises = [promise1, promise2, promise3];

Promise.all(allPromises).then((results) => {
  //if all promises in the collection resolves, `results` is an array of [promise1 result, promise2 result, promise3 result]
}).catch((error) => {
  //if any promise is rejected, `error` will have the rejected value of the promise that failed
  //if multiple failed, `error` will be the error of the first one that failed
});

Promise.allSettled(allPromises).then((results) => {
  //waits until all promises either resolved or rejected
  //`results` will be an array of [promise1 result, promise2 result, promise3 result]
});

Promise.race(allPromises).then((value) => {
  //waits until first promise in array is resolved
  //`value` will be the resolved value of the first promise resolved
}).catch((value) => {
  //waits until first promise in array is rejected
  //`value` will be the rejected value of the first promise rejected
});

Promise.resolve(); //makes a promise that instantly resolves

Promise.reject(); //makes a promise that instantly rejects

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.

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

What The Heck Is Async/Await

async/await is a feature of JavaScript that allows you to write asynchronous code that looks a lot like synchronous code, and under the hood, it’s powered by JavaScript Promises.

Here’s an example:

const getFirstName = async (userId) => {
  //let's say this takes a few seconds
  return "first name";
};

const getLastName = async (userId) => {
  //let's say this takes a few seconds
  return "last name";
};

const getUserFullName = async (userId) => {
  const firstName = await getFirstName(userId);
  const lastName = await getLastName(userId);
  
  return `${firstName} ${lastName}`;
};

These would be referred to as an async functions. There are a few syntactic features to note:

  1. async is added to the beginning of the function definition
  2. Nothing special is done for the return value
  3. Async functions can use other async functions
  4. To get the result of another async function, use the await keyword — you can only use **await** inside an async function!

You can see that getUserFullName reads very much like a regular (synchronous) JavaScript function — it acts like it too! Using await causes the JavaScript thread to stop and wait for the result of getFirstName before going to the next line, and then stop and wait again for the result of getLastName before continuing to the next line.

Here’s another example to further illustrate the blocking behaviour of async/await :

const getLatestPostActivity = async (userId) => {
  const posts = await getPosts(userId);
  const latestPost = posts[0];
  const comments = await getComments(latestPost.id);
  const likes = await getLikes(latestPost.id);
  
  return { comments: comments, likes: likes };
};

In this social media-esque example, we’re getting the activity of the latest post of a given user. This function will run pretty much how it reads:

  1. Wait until all posts are fetched
  2. When complete, get the first one (let’s say sorting by most recent)
  3. Wait until all comments are fetched
  4. Wait until all likes are fetched
  5. Return a value

This readability is one of a few reasons why a developer may favour async/await over raw Promises!

How Async/Await Is Built On Promises

As stated in the beginning, async/await is built upon JavaScript Promises. By appending async to the function definition, even though there may be no Promise explicitly returned, it’s converted into a function that returns a Promise under the hood. It’s accomplished by the following transformations:

  1. The body of the async function becomes the body of the resolver function of a Promise
  2. The final returned result in the async function is called with resolve()
  3. Any await calls to other async functions are smartly converted into Promises as well — any code that comes after an await call ends up in a .then() block, which forces the code that comes after it to wait until it’s done!

The getLatestPostActivity async function example given above is roughly translated into the following using Promises under the hood:

const getLatestPostActivity = (userId) => {
  return new Promise((resolve, reject) => {
    getPosts(userId).then((posts) => {
      const latestPost = posts[0];
      return getComments(latestPost.id).then((comments) => {
        return { comments: comments, latestPost: latestPost };
      });
    }).then(({ latestPost, comments }) => {
      return getLikes(latestPost.id).then((likes) => {
        resolve({ comments: comments, latestPost: latestPost, likes: likes });
      });
    });
  });
};

You can see that a new function is created that returns a Promise, the async function’s body ends up in the executor of the Promise, and each await call inside is structured such that each one happens one after the other. The finally at the end, the return of the original async function is passed into the resolve of this translated Promise!

Need proof that an async function returns a Promise? Fair enough my skeptical friend — here’s a Codepen demonstrating it:

One thing to note is that the argument that follows await in the async function must return a Promise! That means you can use **await** with raw Promises as well:

const setTimeoutPromise = (milliseconds) => {
  return new Promise((resolve) => {
    setTimeout(resolve, milliseconds);
  });
};

const waitOnPromise = async () => {
  await setTimeoutPromise(2000);
  console.log("Done!");
};

Error Handling

So far we’ve discussed async/await assuming no errors happen — in Promise terminology, we’ve discussed only what happens when resolve() is called. But how does reject() fit into this?

Async function use error throwing and try/catch blocks to emulate and handle reject() calls! Here’s an example:

const anAsyncFunction = async () => {
  throw new Error("It didn't work!");
  return "Done!";
};

const main = async () => {
  try {
    await anAsyncFunction();
  } catch(error) {
    console.log(error);
  }
};

main();

That’s about all there is to it. As you would expect in regular synchronous JavaScript code, if there are multiple await calls, if one throws an exception, the rest of the code will not be executed and the code will immediately jump to the catch block.

When wrapping your async function in a try/catch block, don’t forget to still use await !

Async/Await In Loops

There’s a major caveat with async/await that’s easy to miss and hard to debug if you’re not aware of it. async/await plays nicely with for , for : of and while loops. The following is the syntax for each of these loops:

//for : of loop
for (const item of array) {
  await doSomething(item);
}

//while loop
while(...) {
  await doSomething(item);
}

**async/await** doesn’t play nicely with **.forEach()** loops however. .forEach() loops are not Promise-aware, and if you try to await on a Promise inside a .forEach() loop, it’ll just keep running instead of waiting on each iteration as you might expect. Here’s a Codepen demonstrating this behaviour:

As you can see, the “before” and “after” finish first, followed then by all async functions at the same time, which is not what you’re likely going for here.

If you find yourself trying to use a .forEach() loop with async/await , chances are you’re trying to fire off requests one at a time instead of all at once. To accomplish this, I would recommend using a for : of loop as described above.

If you’re looking to fire off a bunch of async requests and wait for all of them, that’s addressed in the next section!

Concurrent Await Calls

Let’s revisit our social media example from earlier:

const getLatestPostActivity = async (userId) => {
  const posts = await getPosts(userId);
  const latestPost = posts[0];
  const comments = await getComments(latestPost.id);
  const likes = await getLikes(latestPost.id);
  
  return { comments: comments, likes: likes };
};

This is great an all, but if you notice, once we fetch the post object, we fetch comments, wait, then fetch likes. But we don’t really need to wait for the comments request to complete before fetching likes. We know how to do this using raw Promises, but how do we accomplish this with async/await ?

Remember our old friend Promise.all() ? Well it’s back to help us here too, with only a slight difference in syntax!

const getLatestPostActivity = async (userId) => {
  const posts = await getPosts(userId);
  const latestPost = posts[0];
  const [comments, likes] = await Promise.all([getComments(latestPost.id), getLikes(latestPost.id)]);
  
  return { comments: comments, likes: likes };
};

Here we directly passed in the two async functions that we want to await for into Promise.all() . If instead you have a list of async requests that you’d like to make, you can employ a similar strategy:

const getAllCommentsOfPosts = async (userId) => {
  const posts = await getPosts(userId);
  const listOfCommentsPromises = posts.map(post => getComments(post.id));
  const allComments = await Promise.all(listOfCommentsPromises);
  return allComments;
};

//or if you want to do it in one line
const getAllCommentsOfPosts = async (userId) => {
  const posts = await getPosts(userId);
  const allComments = await Promise.all(posts.map(post => getComments(post.id)));
  return allComments;
};

Since we already know we can await on a Promise, and Promise.all() returns a Promise that resolves once a list of Promises resolve, allComments will contain a list of “comments” objects once all of the Promises are complete.

Anonymous Async/Await Functions

Sometimes you may want to use async/await in a context where you are not in an async function. It’s a little awkward but you can definitely do it as long as you’re fully aware of how it works. It’ll look something like this:

console.log("before");

(async () => {
  await delayedConsoleLog("first");
  await delayedConsoleLog("second");
  await delayedConsoleLog("third");
})();

console.log("after"); //this actually gets printed right after "before"

Since await can only be used in an async function, so have to (slightly awkwardly) wrap your function in a named or anonymous function and then call that.

What’s important to note is that since you’re initializing an async function inside synchronous code, the surrounding code will continue to execute. So the output of this code would be “before”, “after”, “first”, “second”, “third”.

Converting Promises To Async/Await Functions

Trying to convert plain Promise code into async functions can be a great way to get some practice using async/await . Let’s walk through converting the following to be using async/await :

const getNumberOfPostsOfTopFollower = (userId) => {
  return new Promise((resolve, reject) => {
    fetchUser(userId).then((user) => {
      const followers = user.followers;
      return fetchUser(followers[0].id);
    }).then((topFollower) => {
      const numPosts = topFollower.posts.length;
      resolve(numPosts);
    }).catch((error) => {
      reject(`Error message: ${error}`);
    }).finally(() => {
      console.log("Finally done!");
    });
  });
}

In this example, given a user, we’re fetching their followers, getting the top follower and seeing how many posts they have. We also catch any errors and print to the console after everything is done.

To convert this function to be an async function, let’s bring together everything we’ve covered in this article!

  1. We don’t need to specify the Promise object — the executor function becomes the body of the async function
  2. Replace all .then() blocks with await
  3. Use a try/catch instead of the .catch() block
  4. return the value that was being passed into resolve()
  5. Move the .finally() block code to be right before the return

With all of these changes, we end up with the following:

const getNumberOfPostsOfTopFollower = async (userId) => {
  let user;
  let topFollower;
  
  try {
    user = await fetchUser(userId);
    topFollower = await fetchUser(user.followers[0].id);
  } catch (error) {
    throw new Error(`Error message: ${error}`);
  }
  
  const numPosts = topFollower.posts.length;
  console.log("Finally done!");
  return numPosts;
}

Remember that when it comes to try/catch, due to scoping, you can’t define your variable in the **try** block and try to use it after the **try/catch** . So don’t forget to use let like in this example!

Final Thoughts

Async functions are highly readable in comparison to Promises when it comes to chains, especially in scenarios when you need to use results from one or two requests in a third one, for example. It’s also really simple to throw in an await Promise.all(...) when you need to fire off concurrent async functions.

However, don’t be afraid to mix and match Promises and async/await ! If you’re finding that in your particular use case that it’s more readable or usable to use Promises, that’s okay. I’d give converting them a shot, but if it doesn’t make sense in your specific context, no problem!

Also, when using async functions, don’t forget the best practices that apply to software development in general! Keep your functions as small as you can and with clear purpose.

Happy asyncing, and remember — good things come to those who await !

Cheat Sheet

Basic Async/Await

Async/Await Using Promises

Async/Await Error Handling

Async/Await For : Of Loop

See the Pen Async for : of by jksaunders (@jksaunders) on CodePen.

Async/Await While Loop

Async/Await Inside Synchronous Code

#javascript #web-development

JavaScript Promises and Async/Await Cheatsheet - Beginner to Advanced
26.30 GEEK