We will study this through two demonstrations: first with a sequenced and synchronous loop execution and then with a parallel and asynchronous loop.
- 🛫 The basics of threading and concurrency
🛫 The basics of threading and concurrency
Shortly and briefly: while using traditional language such as C or Java, sometimes you are doing things, that take some time for the CPU to compute.
During this time, you cannot execute other code than the current instructions. Your program can get frozen and the UI might not responds at all while this operation get completed.
This is completely « normal » and you will have to deal with it.
➡️ How to avoid UI freezing ?
To fix this you can use something called a thread, which allows you to delegate some code execution in sub-process. This leads to non-blocking actions and parallel code execution thanks to multi-threading.
This is the basic of code parallelism and concurrency in standard/former languages.
❓What does that mean ?
But as we saw above, sometimes you will need to do multiple things at time or you’ll have some instructions that can take many seconds to process, which leads to freezes because of thread blocking.
So the only way to deal with that is to delegate somehow code execution to an external handler, continue script execution and then execute a callback code once the execution has been processed.
And that is what is implemented by the engine in something call « The event loop ».
Most of time those are IO instructions but it can be something else.
- For a web browser you have
- For NodeJS you have
writeFileas example, that delegate to the system IO.
Once calling such actions, the system take the orders, execute all the instructions on his own command in sub-processes…
Then the system will call your program back once this is done: this is part of the famous JS Event Loop.
💣 Quickly & Briefly: The Event loop is an engine sequence that is looping indefinitely while the program is running, that will execute statements in a queue given a specific order.
Those statements are provided by code functions and callback instructions.
Callback instructions provide some code that needs to be executed once the system finished the asynchronous code execution, such as reading a file on the system.
We speak of callback instructions but in fact it can be:
- Callback functions, that will receive parameters from the OS once called
✋🏻⚠️ I won’t developed what a promise is here, you could have a look at this very well written guide: https://developers.google.com/web/fundamentals/primers/promises
In the following example we will iterate through an array of topics to demonstrate the difference between blocking loop and non-blocking loop using asynchronous behaviors.
This array contains topics, for each we need to:
- Fetch data from a remote API…
- And then write something in a file.
✋🏻⚠️ All the following code are providing stubs functions which are only delaying the function return statement using JS Promise. This produce the same behavior as deferring a code execution to the system.
In order to use the following snippet, you will need to create a
index.js file copy the snippets into and run a
npm init -y && npm install chalk in order to get this small utility library which gives text color in the console.
The simply run the script with
❶ First approach : « Synchronous » code execution
We start with the « synchronous » approach, this one will be blocking execution within the loop using an async function with the
await keyword as long as the called function haven’t returned.
💡Hint: In fact this is not a really synchronous as you expect. Using
await will just ensure the code is executed sequentially within the function. Whenever you
await, you let other stuff happen, and at some later point continue where you awaited.
This leads to the following assumptions:
- Sequenced program execution
- Nonexistent code parallelism
- The guarantee that for each topic the API call has returned and the file has been try to be written with a success / failure, before going to next iteration.
While this can be nice sometimes for some use cases to guarantee sequencing when content have side-effects on each other, in the following use case, this is not the best way to achieve this goal.
By running the following script in a NodeJS program You can see that the total script execution time is pretty high compared to the deferring duration, but as we chain, the take time is growing linearly.
❷ Second approach: Asynchronous code execution & parallelism
As each of our themes values are unrelated between each others in terms of dependency and side effect, our callback
loopOccurence is almost a pure function, as it doesn’t have side effect on our application. (to be a real pure function it shouldn’t have any side effects at all)
👨🏻🔬 A pure function is a function that taking the same arguments, will produce and return the same result each time. This function is also not causing any side effects outside of it’s scope.
And because of that, I invite you to just see what happen if you remove the
await keyword in the previous snippet as described below.
await keywords are tools to control the execution flow of a function. Removing the
await keyword tell the interpreter to not wait for the function to responds before going to the next iteration of the loop.
This approach leverage parallelism as the JS engine sends the three requests at the same time.
💡Hint: Oh yeah, I forgot, you’re not using await inside of the loop but it doesn’t means you don’t have to await at all, in fact you’ll need to handle errors and ensure that everything complete well. We’ll see that in the next section.
Those are great tools, use them as much as you can.
➡️ Welcome to asynchronicity and parallelism.
The performances and the responsiveness of the application are greatly improved and we can still keep our sequencing inside of each theme loop by first waiting to get the API call return state and then create the file.
Looking at the execution time, this is way better than our first approach.
Why ? Because JS delegate parallel execution, so the script can access quicker to others instructions (loop iterations in our case), which results in better performances.
🎁 Bonus : Using functional programming forEach instruction
We could have also replaced our
for…of loop with a function from functional programming named
forEach. The result would be exactly the same as you can see here.
⚡️🔓The most secure, errors safe and efficient way : map + Promise.all
In fact I was missing something really important above: ensuring that everything worked fine and handling errors.
You can improve the above snippet with the following using a combinaison of the functional programing
map function along with
The first one will helps to map promise that will be passed to the second one. The second one will receive an array of promise an will « fast fail » if any of the promise in the given array fail. This is your error handling scenario.
const everythingOk = await Promise.all(themes.map(loopOccurence));
🚛 Takeaway / TLDR; 📦
await keywords are tools to control the execution flow within a function.
Using asynchronicity can greatly improve your applications performances and responsiveness by using parallelism.
Within loops instructions: you can use
for…of statement to get asynchronous iterations. On the other side: you can use
await keyword or
generators to get synchronous code execution within the function.
People at Reddit for helping me improve this article which was a bit clumsy at first.