View Sidebar
Node.js: Promises vs Callbacks

Node.js: Promises vs Callbacks

October 15, 2016 7:23 PM0 comments

Callback hell

If you know a thing or two about node.js is that it’s an asynchronous event-driven environment that some love and some hate. Node’s approach to asynchronous operations is very different from what you might be used to from languages such as C# or Java where you have threads and everything around that. As you know, Node is single-threaded so if you’re running long-lasting operations on the main thread, the application, be it service or UI will be blocked/frozen for the entire time which in turn leads to poor user experience. So Node was making use of callback up until last year when ES6 introduced lots of new features including promises.

Callbacks are functions which are called at the end of long-lasting operation such as IO operations: reading/writing to a file, calling another service via HTTP(s) etc. Let’s see an example of synchronous vs asynchronous code with callbacks.

var fs = require("fs");

var data = fs.readFileSync('somefile.json');
console.log(data);
console.log('finish');

In the above snippet the final message will be displayed after the file was read, time in which everything else is blocked. Let’s imagine now that this piece of code is inside a service. That means that as long as that file is read, the service is unresponsive.

var fs = require("fs");

fs.readFile('somefile.json', function (err, data) {
 if (err) return console.log(err);
 console.log(data);
 console.log('read');
});

console.log("finish");

The snippet above contains a callback which in this case is an anonymous function, but this can be rewritten in many different ways, with a defined function or with array functions (lambdas) but that is not relevant here. What is relevant is the message “finish” will be displayed on screen immediately while the message “read” will be displayed only after the entire file was read and thus allowing other operations to take place while other long running operations are executing. If you do the same exercise with the service this means you have a responsive service that can serve multiple requests in parallel.
Pretty neat wouldn’t you say? Sure, but there is an issue. Nesting and consequently readability and what is called callback hell. A callback hell or nightmare is when you call a function inside a function inside a function. Let’s have a quick example:

var fs = require("fs");

fs.readFile('somefile.json', (err, data) => {
 if (err) return console.log(err);
 callMyfunc(data, (data) => {
   // some processing
   callMyOtherFunc(data, (res) => {
      //some processing
    });
  });
});

So you can see how quickly it can spiral out of control and honestly I have seen way worse. Sure, there are ways to get around that but still you can still end up in a call back nightmare situation.
So what’s the answer? Well, with the introduction of promises you can now chain those events in an even more natural and expressive way. So the code above can turn into the following:

// There is a promise module for most of the popular node.js modules.
var fs = require("fs-promise");

fs.readFile('someFile.json)
  .then((data) => return callMyFunc(data))
  .catch((err) => console.log(err))
  .then((data) => return callMyOtherFunc(data));

So all of the sudden this became linear, easy to read and to reason about. This is what is called promise chaining where multiple promises are chained together with the purpose that at end you’ll return a promise or throw an exception. Testing becomes naturally nicer since you don’t have the annoying “done” callbacks if you are using the mocha testing framework.
Promises have handlers and states. The states and their corresponding handlers are:

  • pending
  • fulfilled/resolved corresponds to then.
  • failed/rejected  corresponds to catch

Promises come by default in Node but there are libraries that can help you with promises. Most popular ones are Q and bluebird. They can even turn callbacks into promises.
Even though that is nice enough, it can be better by the use of the keywords await/async which makes asynchronous code look synchronous but will not get into that now. The main reason is that people have enough trouble understand Node’s callbacks model and later on the promise model to further confuse with the new features of ES7.

In the mean time, happy promisifying!

 

Leave a reply


Simple Share Buttons