Introduction
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.
Callbacks
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
Then, over time, things got better and when the new JavaScript specification called EcmaScript 2015(or ES6) came out, we got a solution to our callback hell problem – promises.
Promises provide a more elegant solution for writing sequential asynchronous processes. You can read more on promises
here
, 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.
async await
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 async
and 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:
- Put
async
keyword before thefunction
keyword when declaring a new function - Whenever we have a promise to execute we put
await
before the promise call to make the main execution thread wait till the promise returns
async
and await
weren’t a part of the first ES2015 specification, they were added later on. Still, today Node 7 + and most of the modern browsers support them. However, if you’re looking for a full support you might need to use JavaScript transpilers, such as
Babel.
Generators
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:
- Promises
- Thunks
- Generators
- Generator functions
- Objects
- Arrays
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
- Using
async
andawait
, 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
Conclusion
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+.
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.
Happy coding!