Photo by marcos mayer on Unsplash

In Node, I/O operations are handled asynchronously by default, and the original way in which Node handles asynchronous calls is by using callbacks.

Callbacks are just the functions that you pass to other functions as an argument.

const fs = require('fs');
fs.readFile('someFilePath', (err, data) => {
if(err){
//do something when error occurred while reading file
}
//do something here with the file data
})

In the above code, we are reading a file asynchronously and performing certain actions based on whether an error occurred or not.

In Node, there is a convention of using the error-first callback (i.e, the first argument passed to the callback function is the error object), and after that, only other arguments are passed (see the above code example).

Note: A callback does not mean an asynchronous call in the code.

In the above code, we are using a callback function, and it is completely synchronous. So callback does not necessarily means asynchronicity in our code

Using Callback

We will take one asynchronous example and then write it using a callback, and then we will convert it into Promise-based code.

Let's say we have a file that has numbers written in lines.

Now what we will do is read all the numbers from thenum.txt file using the node fs module, then convert them into an array. Then we’ll filter out those numbers that are even by using a callback-based coding style.

Output:

Even Numbers :  [ '102', '104', '106' ]

Using Promises

From ES5, JavaScript has native support of Promise, and I personally feel promises are a better way of dealing with async code than callbacks. In callbacks, we can get stuck into something called callback hell, which can really make our code harder to read and understand.

Instead of passing a callback as an argument and handling the error in the same place, promises allow us to handle success and error separately and also allows us to chain multiple async calls instead of nesting them, which basically prevents callback hell.

Output :

Even Numbers :  [ '102', '104', '106' ]

So we have achieved the same result here too and also in a much easier way.

But the conventional way of working with async code in Node is through callbacks. Also, thousands and thousands of libraries are written using that style, and developers using your libraries might assume that you provide them with the error-first callback interface before they will use your library.

So the better way is to give the user both the interfaces — the conventional callback as well as the new promises — so they can use both or the one in which they are more comfortable.

Callback and Promise

What we will do is mark the callback parameter as optional, so if the user passed the callback, he or she will get invoked with the error and data. Otherwise, a promise is resolved or rejected, and then we have to handle that.

Output :

Even Numbers :  [ '102', '104', '106' ]
Odd Numbers : [ '101', '103', '105' ]

In the above code, we are assigning the default value to the callback function in the function readFileAsArray, and whenever we are using Promise, we do not pass that callback function. Then that default callback function is used, which is of no use for us.

But when a callback function is passed, then our passed value overrides our default callback value. Then this passed callback is invoked, and we can manipulate data in that callback function.

We are printing even numbers in a Promise-based call and odd numbers in a callback-based call.