Concurrency and Asynchronous Programming in JavaScript

CodeWithAL
10 min readSep 29, 2019

JavaScript is a single-threaded asynchronous (but not parallel) programming language yet everything we do on the web tends to be blocking or time-consuming.

Our code runs fundamentally in a single threaded event loop environment. So this means in any given instance only one of our functions or lines of our JS code can be running. Although this looks like a tremendous limitation in fact it’s a huge freedom, it’s a freedom from having to manage all the possible complexities that may have caused otherwise in parallel programming. Concurrency doesn’t mean at any given moment something is happening but more about two higher level sets of tasks are occurring within the same time frame.

All these means that asynchronous programming is an essential skill for any JS developer.

Today I will be focusing primarily on the syntactic sugar provided by async/await via ES2016 (ES7). But before we get there, let’s have a look into things that we really need to understand from the ground up starting with the event loop, callbacks and promises. It is crucial to understand the event loop if we want to have a proper grasp on async/await first. So let’s get started!

(Also If you wanna learn more about event loop, here i’m providing you with 2 links covering in-depth explanation of event loop; one from Jake Archibald and the other from Philip Roberts)

So what is an event loop and what’s it relationship with async/await. Basically both the browser and node.js are always running a single threaded event loop. On the first go around of the loop it will run all the synchronous code but it would also queue up the asynchronous events to be called back later.

Here’s a hypothetical function that getData(callback) we need to run but first we need to get some data from the network, the event loop says “okay I’ll keep doing my thing while you do your thing.” In a separate thread pool then at some point in the future getData(callback) will finish and let the event loop know that it’s ready to be called back. Now this is where things get interesting if it’s a macro task like setTimeout or setInterval it will be executed on the next event loop but if it’s a micro task like a fulfilled promise then it will be called back before the start of the next event loop.

The Event Loop!

On the code above, nothing here has any actual time delay so intuitively we can expect that each line of code would be executed one after the other. But as we mentioned before here things are happening by the nature of event loop and that’s not how things work in the event loop. If we execute this code line by line, we can see that first line gets logged to the console right away because it’s running on the main thread then for the second line it’s being queued for a future task as a macro task, then the promise will be queued also for a future task as a micro task immediately. And finally the last console.log gets executed right away. So even though the setTimeout call back was queued up before the promise the promise still gets executed first because of the priority of the micro task queue over macro task queue.

Promises!

Promise is a representation of a future value, more of a container that we wrap around a value. It doesn’t matter if the value is here or not. we continue to reason about the value the same way regardless of the value is here or not. they are a container around a value that eliminates time as a complexity.

Let’s check out a Promise by seeing a promise based API and see how we can create our own promise from ground.

On the code below I’m using fetch. It is a browser based api but it’s also available on node via the node fetch library(You can try the code in your browsers console without importing fetch), it simply allows us to hit an HTTP endpoint and have the response returned to us as a promise. Fetching data from a remote server is always going to be an async event so we can queue up the promise then provide it with a callback to convert it to JSON. The good thing about promises is that we can chain them all together, converting the data to json is also a promise so we can return that promise then callback and only then in the next one we’ll have the actual user data as a plain JS object. If we execute this code you will see as it runs our console.log first and then it retrieves the data from the API and console.log that afterwards.

Another cool thing about promises is that you can catch all errors in the chain with just one single function. We can try this out by adding catch to the bottom of our promise chain and it will handle errors that might happen anywhere within our code. If our code were callback based we’d have to have a separate error handler for every single one of the asynchronous operations so if an error is thrown anywhere in our code it’s going to bypass all of the future .then() callbacks and go straight to the catch callback. On the example below we can see the structure.

But also promises mean further complication in our code and an increased tendency to screw things out. So in our example below, first we are setting a log function that determines the elapsed time between each line of code. And afterwards we are setting a codeBlocker function which basically consists of from a while loop, that is arbitrarily looping a billion time. If we run this on the main thread, it will block all of the code from executing until a billion loop is completed, so we will check this out by placing the codeBlocker function in between two console.log. As you can see from the output our script is basically frozen until that while loop is complete.

So let’s wrap this code in a promise so we can get it off the main thread and execute it as a micro task, this one is a bit more trickier tho. So we create a new promise and implement our code inside that promise and we have it resolved to that value upon completion. You might think that, just because we are wrapping this in a promise, we’re going to execute it off the main thread. But the actual creation of the promise and that big while loop is still happening on the main thread. Only the resolving of the value happens as a micro task.

so the first Synchronous line gets logged right away and you might think the second one should too, right? But still there is a 545ms delay because that while loop is still blocking on the main thread, so to ensure that all our synchronous code runs as fast as possible, we’re going to refactor our code again one more time to say promise resolve then we’ll run the while loop inside of that result promise’s callback. By putting our code inside of a resolved promise, we can be guaranteed that it will be executed after all the synchronous code in the current macro task has completed if we go ahead and run our script again we can see our console logs right away and then finally the promise resolves after 565 ms.

async/await

Now that we covered the stuff we need to know to use async/await properly. Promises are huge improvements over callbacks, that is for sure. But promises can still be really hard to follow, especially when you have a long chain of multiple asynchronous events. async/await is basically a syntactic sugar to make our asynchronous code read like synchronous code.

Let’s have a look at the async part of the equation;

When we put the async keyword in front of our function, this indicates that now we have a function that returns a promise of nothing. So whatever gets returned inside the function will be a promise of that value. Below I’ll use a getIngredient function just to mimmic what an API based looks, would look like in our scenario. So basically when we pass the ingredient to getIngredient function, the function will resolve to the value of the name of the ingredient from the ingredients object. And just to be clear if we didn’t use the async keyword we could write this function by just returning a promise that resolves to this value so when we use the async keyword the magic that happens is that it takes the return value and automatically resolves is as a promise but, of course that’s not just everything that it does, it also sets up a context for the usage of await keyword. The real power of an async function comes when we combine it with the await keyword to pause the execution of the function.

Now let’s write another async function called makeSmoothie what we want to do here is to get multiple ingredients and then combine them together as a single value. Instead of chaining .then callbacks, we can just have a promise resolve to the value of a variable, await is like saying pause the execution of this function and tell the getIngredient promise resolves to a value at which point we can use it as variable a and then we’ll move on to the next line of code. After we get another ingredient then we will turn them together as an array. One of the annoying things about promises is that it’s kind of difficult to share results values between multiple steps in the promise chain. But async/await solves this.

Below you can see you examples of the same code one with async/await and one with regular promises.

As you can see there’s a lot more code and a lot more complexity. Now if you’re already an expert, the code on the left is already making a big mistake. That’s failing to run the code concurrently. If we go back to the code we can see that first we wait for banana to resolve and then we wait for chocolateMilk to resolve but we could get both of these things at the same time. We should only be waiting one thing after the other if the second value is dependent on the first value. for example you need to get a user ID first so that you can retrieve some data related to that user ID from the database.

Let’s imagine we’re making these calls from a remote API an there is about a second of latency if we have just run our code with the delay we would have get our results in apr. 2 seconds, one second for the first getIngredient call and one second for the second getIngredient call. In the example below we can see the 2 seconds of delay.

But the whole point of the event loop is to avoid blocking code like this. We know that an async function always returns a promise so instead of doing the calls one by one we can use the Promise.all(). basically we put all of our promises in an array and pass that array as an argument to Promise.all() and await! This will run our promises concurrently and then will resolve the values as same as of the initial index in the array. So instead of getting our results in 2 seconds, now we will have our results in one second. A double in speed, pretty good right?

The other nice benefit of async/await is error handling. Instead of chaining a catch callback to our promise chain we can just wrap our code in a try-catch block. This offers significantly better flexibility when handling errors. Let’s keep on working with our code from the last example and throw an error in between, in the example below we can catch that error down in our catch block.

First thing we probably wanna do is to console.log the error and then we can either catch the error and throw another error, or we can catch the error and return a value. The decision made here will dictate the control flow for the consumer of this promise. Basically returning a value is like ignoring the error and then providing some replacement value. So the consumer of the promise won’t get an error but instead they’ll get the result value inside of the then callback. Contrary to that if we throw an error inside of our catch-block it will break the consumers promise chain and be handled by their catch callback.

Some Useful Tricks

Now let’s do some tricks to carry our skills to the next level!

Let’s image we have our name identifiers(fruit names) as strings and we want to retrieve all these information(fruits) from our database we can use Array.map() to convert them to an array of promises and then resolve them all concurrently using Promise.all() That looks great in fact but we need to be careful when using async/await in a map or forEach loop, because it will not actually pause the function in this context, Normally we would expect this loop to stop if we do await getFruit but that’s actually not what happens in this case instead it will run all these promises concurrently so this might not be our expected behavior.

If we want to run a loop and have every iteration in that loop to await a promise we need to use a traditional for loop. We can write asynchronous functions and we can write a for loop inside that function and use the await keyword inside the loop when we write our code. It will pause each step of a loop until that promise is resolved.

But we will probably want to run everything concurrently and one cool thing that we can do is use the await keyword directly in a for loop! if we have a promise that we know will results to an array we can just use the await keyword directly in our loop so which will await the array of items to resolve and then loop over them immediately after.

As you can probably guess we can also use the await keyword directly in our conditional statements. On the left side of the conditional we can await the result value from a promise and then see if it’s equal to some other value. this gives us a very concise way to write conditional expressions when working with promises.

Thanks for reading, I hope that you enjoyed the read and maybe even learned a few things out of it. See you next time…

--

--