Node.JS Concurrency with Async/
Await and Promises!
Must you survive the ‘callback hell’ to achieve ‘concurrent bliss’? — well, not anymore 😉
What’s concurrency?
TL;DR Concurrency is at the heart of Node.JS
Concurrency is how Node.JS handles some of the world’s heaviest, scalable workloads on it’s seemingly ‘single-threaded’ event-loop. — I’ll cut short here before you fall asleep; you didn’t come here to watch me praise the good name of the Node now did you?
— Concurrency in very simple terms means that two or more processes (or threads) run together, but not at the same time. Only one process executes at once.
— Parallelism on the other hand means that the processes (or threads) run in parallel (surprise surprise); meaning they start at the same time and execute alongside each other at the same time.
Now you’re probably thinking, what’s better? Well the thing is, they both work and different programming paradigms and languages support different models (and sometimes even both!) — And the choice really boils down to your specific application requirements. Regardless, Node.JS only supports an ‘Asynchronous event-driven’ programming paradigm (i.e. Concurrency) and that’s all you’ll ever need to write highly efficient Node applications.
The ‘Callback Hell’
Now if you’ve used Node before, you know what this is.
I recommend you open up your favorite code editor and try this out as we go along, because callback hell isn’t called callback hell for no reason — To the untrained eye it will look like 💩 code. (And it is 😜)
Now before you look at this code, let me explain what we’re going to do.
I’ll be using fs.readFile()
and fs.writeFile()
from the standard Node library. If you’ve ever performed any kind of file I/O in node, these two functions need no introduction. For those of you who don’t know what these are, they are two asynchronous non-blocking I/O functions for reading and writing from a files, meaning they’ll perform a file I/O asynchronously without blocking the main thread. (*non-blocking I/O)
Non-blocking I/O*
If you’re coming from languages like Java or Python you know that when you perform an I/O function, the next line of code will not execute until that particular I/O request is completed. But with Node, an I/O functions is non-blocking; it will continue in the background asynchronously and the program will simply move on to the next line of code without waiting for the I/O function to complete. And this is why we pass in a ‘callback function’ to execute after our asynchronous I/O function completes. — Now if you’re new to this whole concept, it might be a bit confusing so bear with me and it’ll all make sense when you try out some code.
What we’ll be doing:
- Read from a file asynchronously.
- Display the contents.
- Update the contents and write to the same file asynchronously.
- Display the updated content.
Here’s another way to write this without breaking it down into separate functions. It really doesn’t matter how you write it though, it’s still stacking up massive technical debt at each line 😂
— make sure you make a ‘sample.txt’ file and put some random text and place it in the same directory as your .js file.
Now as you might imagine, if such a simple concurrent task produces this magnitude of horrendous code, imagine this process repeated over say, a Database I/O process. — Sounds pretty bad eh? well, it’s probably worse than it sounds 😂
Don’t worry if nothing makes sense at first glance, asynchronous code with callbacks won’t make sense until your brain sort of figures how to wrap itself around the whole concept of code that doesn’t execute line-by-line like you’d expect. I recommend you take some time and try out those two code snippets on your own and try to really grasp what’s going on. Nothing is better than hands-on learning folks! 😉
Enter: The JavaScript Promise!
Promises do exactly what they are named after, they promise the eventual completion (or failure) of an asynchronous block of code — basically, it gives us an escape route from the callback hell to write much cleaner code -- which seems to run line-by-line as synchronous code we’re used to seeing but actually runs asynchronously behind the scenes. I promise you’ll understand what that peculiar definition stands for when you go through the code in this section.
The promise syntax
Whatever is passed into resolve()
callback will be available to the .then()
function of that Promise object. Anything that’s passed to the reject()
callback of a new Promise can be caught in .catch()
of the Promise object as shown in the example code above. Again I highly encourage you to stop here for a bit and try out that snippet for yourself to really understand it.
You probably see how readable and maintainable our concurrent code is now as opposed to the callback-hell examples 🙌 This is called Chaining Promises by the way. You can notice how we could keep on following the same pattern and sort of chain an infinite set of Promises, all asynchronous, but runs line-by-line as our brains expect code to run!
Enter: Async and Await; The dynamic duo!
These bad boys are all about making async code even more readable and maintainable — so much so than chained promises. These allow us to write asynchronous code that is synonymous to “normal” sync (blocking) code we’d usually see in syntax in other programming languages.
Async and Await requires a couple of things:
await
can ‘wait’ for aasync
function to resolve or reject a value and allows us to handle the return value without using.then()
or.catch()
async
is a special keyword attached to function declarations to let the Node interpreter know that it’s an asynchronous function.
e.g.async function foo(){...}
orasync () => {...}
- Important!
await
can only be used inside an async function.
And there you have it! Hopefully you went through the code snippets I’ve written and played around on your own a bit.
That said understand that there’s a lot more to Promises and Async/Await in NodeJS. This article is only meant to be a simple introduction to the world of concurrent programming with Node. So go forth, Google and learn!
— There’s also a new kid on the concurrent block called Generator Functions. I’ll just leave that here if you’re interesting in reading further on the topic 😄