Using promise in Node.js

Photo by: Jasmine Carter

Asynchronous? JavaScript is always synchronous when you executing a JavaScript block of code. In this case, no other JavaScript code will be executed concurrently.

Understand asynchronous

JavaScript is asynchronous if the function is doing some networking, for example, AJAX calls.

A quick example could be helpful:

console.log("a"); // networking or AJAX function
db.get("select * from table", function (result) {
  console.log("b");
});
console.log("c");

will show the following in debugger console:

 a c b

In the above code, a happens first, then the db.get function starts, it return right away and the rest of the code after the function (ie, c) will execute. Meanwhile, the networking / AJAX operation is going in the background. When the networking / AJAX request has completed and the response has been collected, then b happens.

These two posts are helpful as well:

Callback vs Promise

Callbacks

Given that JavaScript is asynchronous when doing networking, and we really need to execute some code after the networking completed (success or error), it’s time to use callback functions. In the first example, ‘b’ is actually an anonumous callback and we already know ‘b’ won’t happen until the response has been. Here is another example:

// define function with callback argument function
another_example(arg1, callback) {

  // retrieve some data from table
  var result = db.get('select \* from table where attr=arg1');

  // callback is triggered and pass the result
  callback(result);

}

// call another_exmaple
another_example(1, function(data) {
  console.log(data);
});

The problem with callbacks is that when you have lots of callbacks, you will probably end up with a callback hell which looks like:

function1(function(x) {
  function2(function(y) {
    function3(function(z) {
      function4(function(i) {
        ...
      });
    });
  });
});

Promises

A promise is a a proxy for a value not necessarily known at its creation time. With promises, the calling code can then wait until that promise is fulfilled before executing the next step. To do so, the promise has a method then, which accepts a function that will be invoked when the promise has been fulfilled. Promises require an additional abstraction layer, which means you will rely on a library.

A promise can be:

  • fulfilled - The action relating to the promise succeeded
  • rejected - The action relating to the promise failed
  • pending - Hasn’t fulfilled or rejected yet
  • settled - Has fulfilled or rejected

Our previous example could be written as:

function another_example(arg1) {
  db.get("select * from table where attr=arg1").then(function (data) {
    console.log(data);
  });
}
another_example(1);

Real life example

The following is Node.js scenario when I was trying to move a file from one folder to another within the same bucket of Amazon S3.
Scenario

In test bucket, I had a list of files stored in a folder called unsold, I need copy them all from unsold to a folder called sold, and delete them afterwards. (Amazon S3 node.js SDK doesn’t provide any method can move file directly >_<)

Async

Async is a module which provides straight-forward, powerful functions to work with asynchronous JavaScript. Note: Async is not using promise pattern to handle callbacks, it uses some common patterns like parallel, series, waterfall instead.

exports.moveFile = function (files) {
  // for each file in files
  async.each(
    files,
    function iterator(file, callback) {
      // initialize params
      var copyParams = {
        Bucket: "test",
        CopySource: "test/unsold/" + file.filename,
        Key: "sold/" + file.filename,
      };
      var deleteParam = {
        Bucket: "test",
        Key: "unsold/" + file.filename,
      };

      // copy file
      s3.copyObject(copyParams, function (err, data) {
        if (err) callback(err);
        else {
          // delete file if no error with copying
          s3.deleteObject(deleteParam, function (err, data) {
            if (err) callback(err);
            else {
              console.log("delete", data);
              callback();
            }
          });
        }
      });
    },
    function allDone(err) {
      //This gets called when all callbacks are called
      if (err) console.log(err, err.stack);
    }
  );
};

Q

Q is a tool for creating and composing asynchronous promises in JavaScript.

The following snippet is a re-write of previous exmple using Q:

exports.moveFile = function (files) {
  files.forEach(function (file) {
    var copyParams = {
      Bucket: "test",
      CopySource: "test/unsold/" + file.filename,
      Key: "sold/" + file.filename,
    };
    var deleteParam = { Bucket: "test", Key: "unsold/" + file.filename };
    var copyObject = function (copyParams) {
      var deferred = Q.defer();
      s3.copyObject(copyParams, deferred.resolve, function (err, data) {
        if (err) console.error(err);
        else return defered.promise;
      });
      return deferred.promise;
    };

    copyObject(copyParams)
      .then(function (res) {
        s3.deleteObject(deleteParam, function (err, data) {
          if (err) console.error(err);
          else {
            console.log("delete", data);
          }
        });
      })
      .then(function (err) {
        if (err) console.log(err, err.stack);
      });
  });
};

  TOC