One of the building blocks of the Node.JS ecosystem are asynchronous processes. Asynchronous processes in Node.JS are non-blocking, which means the main execution threads does not wait for the asynchronous process to finish.
This fact introduces coding style that is fundamental to working with Node.JS – Continuous Passing Style, or CPS for short. With CPS, you start an asynchronous process and pass a callback function to it, the callback gets executed when the process is finished.
The most common problem that developers face when working with CPS is called callback hell. Callback hell is when your code start growing horizontally rather than vertically, slowly shaping into a pyramid:
As you can see, the method above is taking a shape of a pyramid and it only does three simple things:
- Read file
- make http request
- write to a file
Most of the Node.JS developers are familiar with callback hell so I’m not going to get into too much detail.
Promises provide a more elegant solution for writing sequential asynchronous processes. You can read more on promises
, but in a nutshell, promises let you avoid the pyramid structure of your code by chaining multiple asynchronous executions with
then.You pass a function to
then which can return another promise, thus continuing the chain.
You can already see that the code looks much better compared to the callback-based solution. And if we keep adding promises to the chain the nesting level of the promises will stay the same, it won’t grow horizontally.
Promises are great, but in my opinion that’s still a lot of boilerplate code just to accomplish trivial asynchronous sequence. Wouldn’t it be great if we could make asynchronous calls one after the other, similar to how it’s done in other programming languages, like Java?
Good news is there’s a new EcmaScript proposal that addresses that problem. I’m talking about
await keywords. Let’s look at the example:
This looks a lot better! We were able to reduce our previous operation to just 3 lines of code and it’s more readable. To make it all work we had to do 2 things:
asynckeyword before the
functionkeyword when declaring a new function
- Whenever we have a promise to execute we put
awaitbefore the promise call to make the main execution thread wait till the promise returns
One last solution I want to mention has to do with ES2015 generators. Generators are functions that let you use
yield keyword to pause the execution of the generator function. Let’s take a look at the example:
Interesting thing to notice here is that you need to put
* after the
function to declare a generator. Also, whenever the execution code inside of the generator encounters
yield keyword, it pauses the execution and awaits to be resumed again.
Here’s a sample code on how we would use our generator:
Calling a generator function returns a new instance of that generator. We call
next method on that instance three times: one to start the function execution, and the other two to resume after
yield keyword is encountered. Another thing you will notice is that
next function returns an object. The object returned from
next has the following format:
Generators by themselves do not make our asynchronous code any easier, we need to use them with third party libraries. One of these libraries is called co .
Co let’s you use generators to write your asynchronous code in a linear fashion. Co works with several types of yieldables:
- Generator functions
Let’s take a look at the example of using co with generator function and promises:
From the code above you can see that using co and generators lets us write code which seems to be executed in a linear fashion. However, truth is that there’s a complex callback system that wraps our generator to make it possible. But you don’t need to worry about that.
async await vs Generators
One advantage of using co over
async is that co also deals with other yeildable types such as thunks, generators and array of promises, while
async only deals with promises. Plus, co has built-in helper methods to execute an iteration of async tasks in a sequential or parallel fashion.
So to summarize:
- Using vanilla callback-based approach to asynchronous execution might lead to deep nesting problem called ‘Callback Hell’
- Promises are a step in the right direction to solving the deep nesting problem, but they still generate a lot of boilerplate code
await, we can reduce our async execution code, while also making it more intuitive, however this approach might not be supported by older browsers
- Another method of of achieving a linear code in our asynchronous execution is using generators combined with libraries like co
In this brief post we talked about the tricks and techniques we can use to write simpler and shorter asynchronous code.
The beauty of Node.JS platform is that there are multiple ways to achieve this simplicity. And a lot of it also comes from the new syntax introduced by EcmaScript 2015+ .