Photo by Umberto on Unsplash

Asynchronous Code Execution in JavaScript

5 min readMar 8, 2022


Before we start working with requests, we need to first learn about asynchronous code execution.

Lets say that you want to do your homework, but you’re also very hungry. You have some frozen pizza in your fridge. The instructions says that you have to microwave it for 60 seconds before you eat it.

So here’s what you do: You go to your fridge, take the pizza out, and then you put your pizza in the microwave. Then, you stand there idly for 60 seconds, take the pizza out, eat it, and then start doing your homework. Right?


It’s not logical for us to just wait there for 60 seconds for the pizza to warm up when we could be doing something useful instead, like doing our homework. And that’s what asynchronous execution is: when something takes a lot of work to complete or takes time to complete, we don’t want to stall our program out: We want to be doing other things instead.

I’m not going to bother explaining all of this since it’s a vast and complicated topic, so I’ll let the Mozilla Developer Network do the job.

Did you read it all? If not, then go back. Seriously, although this stuff is confusing, it’s going to be an important part of what we’re going to be working with next and what you’ll be working with in the future. Taking a few minutes reading it now will save you a few hours of debugging later. So read it. Do it!!!

Key Points

Now, I’ll review over some of the key points and ideas you should keep in mind when working with asynchronous code execution.

Promises vs Callbacks

There are 2 main ways to execute and handle asynchronous operations: using callbacks and Promises. They both work somewhat the same way, requiring you to pass in a function to execute once whenever a future value is computed or received.

The main difference between callbacks and Promises is that in a callback, the way the callback is executed is up to the parent function, not you. This is called inversion of control. Take this example:

The function has a 50% chance of executing the callback, and 50% chance of doing nothing.

The parent function has complete control over when, where, and how to execute the callback. And while most libraries will keep it simple and follow a consistent pattern of, for example, just executing the callback at the end with the computed value, you can never be so sure when executing someone else’s code.

On the other hand, when you have a Promise, you know that the Promise is either going to resolve into a single value or return an error. You can then decide what to do with that single value instead of letting the parent function decide. Promises force all developers using it to follow a single convention.

We are guaranteed a value or an error, and we can decide what to do with either!

Promise.all() and Concurrency

There are some other nuances too.

With Promises, you can have multiple Promises execute concurrently, while with callbacks, you can only perform one after the other. Take this example where we migrate entries from our old database to a new one:

This callback style code takes anywhere from 4 seconds to 8 seconds to complete. First, we get Bob’s email, and then we get his phone. We then store both pieces of information onto the new database.

We can’t send both requests at the same time because we aren’t guaranteed to receive both values at the same time, and we don’t know when both values will be received. Notice the Math.random(): if we execute both one after the other, can we be sure that the first getFromOldDatabase will complete before the second, unless we use the function’s callback?

With Promises, we can simply use Promise.all().

Because Promise.all() executes all it’s given Promises concurrently (unlike our callback styled program, which executes one after the other), the resulting time taken of our Promise based program ranges only from 2 to 4 seconds with a mean of 3.33 seconds vs the 6 seconds of the original!*

*(The mean is 3.33 seconds because Promise.all() has to wait for ALL it’s Promises to complete before executing, so it takes 2 + Math.ceil(Math.random() * 2, Math.random() * 2) amount of time, which is on average, 3.33 seconds. I too lazy to explain why, but here’s a proof.)

Async and Await

Ok, Promises are great and all, but the idea of passing in a function for our newly computed value is giving me some callback PTSD…

Luckily, we have another method of working with Promises! Instead of using .then() functions, we can use the await keyword to wait for a Promise to finish, and then store that Promise’s value in a variable.

Compare the .then() and the async await. The async await looks just like regular synchronous JavaScript!

Also note that in order to use the await keyword, we need to use an async function, which is why I created a new function and then immediately invoke it.

Oh, and remember that async functions automatically wrap its body inside a Promise. Sometimes it’s more convenient or intuitive to use a Promise object constructor, and other times we can just wrap whatever inside an async function.


Yay! So now we know how asynchronous JavaScript works! To go back to our pizza homework example, we now know how to do homework while waiting for our microwave.

I think we’re ready to work with requests. In the next tutorial I’ll show you how to use a request library called Axios to send GET requests inside your React application.