When I was in college, I had no idea what 'asynchronous' meant. Coming from the world of C++, PHP, and Java, the word 'asynchronous' was completely alien to me. I had a vague understanding of multi-threading in Java and I dreaded it. I've come a long way from there! 😌
Events, event handlers and callbacks.
This is from pre-historic times. If you have enough experience, you must've come across event-driven systems - Visual Basic,
OnClickListener() in Android,
onchange behaviour in HTML, etc. Since Node.js is primarily an event-based runtime environment, all it had initially were events and event handlers. Event handlers are just functions that are triggered once a certain event is fired/emitted. Just like the
onChange behaviour in HTML.
Simply put, it means something that occurs after an unknown amount of time, so don't expect immediate results.
For example, "Mom, can I have five dollars?"
Putting my hand out for money is me expecting her to immediately respond by giving me money (synchronous).
Realistically, she will look at me for a moment or two, and then decide to respond when she wants to (asynchronous).
Due to the asynchronous nature of JS, the system wouldn't wait while, say, you get some data from a database (It was really difficult to wrap my head around and get used to this initially).
However, events enable you to put your work on hold when Node.js realises that it is an async task you're performing; and then lets you resume your work when the task has been completed and data is available.
The issue with callbacks.
There are hardly any practical applications that may not involve async operations. The advantage of using Node.js is that time-consuming async operations don't affect the performance of your server. The server won't hold off (or starve) one request till another one is completely processed and its response is sent. As soon as Node.js realizes that an async operation is to be performed, it'll delegate a worker process to handle the operation and immediately start processing the next request. This gives a terrific boost to the speed of the system. If your server is getting a lot of requests, and each request requires some async operation (say, database queries), this turns out to be significantly efficient.
However, this efficiency came at a great cost. Writing industry-grade applications with just events, event handlers and callbacks is not easy. Callback-hell is the biggest problem with callbacks that leads to decreased code-extensibility, reusability and manageability.
Coming from the object-oriented background of Java, I found it very difficult to get used to writing code involving callbacks - how you have to split the code into a separate function, the callback function. The struggle was real during that time.
Frustrated by writing asynchronous code with callbacks, developers started finding creative ways to write better, cleaner code. For example, we used to use async.io at my workplace. It has utility methods like
async.waterfall() is the most interesting one to me. It lets you chain async functions together so that one function's output is the next function's input - kind of like the human centipede but with functions. 😅
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
A promise is just a wrapper around callbacks. An ingenious wrapper where we make a shift from using functions for storing the next code to using an object. The next function to call (the callback), instead of passing it into a function, we attach it to an object - the promise object. This object is then responsible to pass the callback function as an event handler to the concerned event.
You can instantiate a promise object from any callback-based function. Thus, you can always go from a function-based approach to an object-based one.
The significance of this is that your code turns from nested blocks of callbacks to a linear chain of
It is a lot easier to make modifications to your code when it is written in a linear sequential manner (the very reason we love synchronous code) than when it is written in nested blocks. Your code instantly becomes readable, predictable and 200x more manageable.
Read this article for more information on Promises:
If the Promise object sounded like magic, and you are interested in understanding its internal working, you might be interested in this article.
Generators were introduced in ES6 (2015) along with promises. But, I believe not many people know about them or use them often. They are functions that return generator objects. A generator object is an iterator. An iterator is anything that implements the iterator protocol.
The iterator protocol says that an object can be called an iterator if it has the
next() method that is supposed to do a very specific job; get the next value of iteration/sequence. If you're familiar with Scanner in Java, it's an Iterator (Although it breaks Java design principles)
So, a generator object is basically an object that has this
next() method. And generator functions are just functions that return generator objects. If you've ever used
xrange() in Python 2.x, that's literally a generator. A very good example of a generator will be a Fibonacci generator.
Read the Mozilla docs for more information on generators and iterators. Also, this in-detail post on generators on Medium:
Now that we know what generators are, we make coroutines simply by adding promises to the mix.
Please note that the code has started looking very similar to its synchronous equivalent. It just needs some supplementary parts. To take care of that, people came up with a few coroutine libraries such as CO.
This part might have been pretty difficult to wrap your head around. It is pretty convoluted. But you might want to read this article if you're interested:
Soon, in ES8 (2017), async-await was announced and that made writing coroutines redundant. Co-routines died out before they could become a thing. Many people today probably don’t even know about them.
This code looks exactly similar to the synchronous equivalent. And I’m awe-struck when I think about how much we hated callbacks, and how much we love structure, that it led us from callbacks to async-await. I'm mesmerized by the transitions that happened around Node.js in a such short period and I needed to talk about it.
Now, the code looks really simple. Write your code using functions and when you're going to perform an async task, just use the
My mentor probably understood that well. And that is why he set me up on this journey to find and feel the true essence of Node.js.
JS-veterans, if you find any inconsistencies in this piece, or would like to add more. Or simply want to talk, feel free to comment or DM me. JS-newbies and JS-virgins, I hope I've sparked an interest in the JS community in your minds. Feel free to reach out in case of any doubts.
Did you find this article valuable?
Support Gaurang Pansare by becoming a sponsor. Any amount is appreciated!