Learning ES6 Promises (An Old Dog Learning New Tricks)

Over the last couple of weeks, I’ve been working on creating a simple ReST API in Node. This is only interesting because I’ve been a solid Java developer for many years and have been seriously dragging my heels into the world of Javascript. This post is an account of some of the questions I’ve had along the way, and answers to those questions I’ve managed to find. Please feel free to comment with suggestions or alternate approaches. This is not meant as an instructional article at this time.

Format for Promise Chaining

A simple first question is – how should one layout/indent a promise chain? Most automated code formatters should take care of this, but the scheme I landed on is the following:

somePromise()
  .then((result)=>{
    return someOtherPromise()
  })
  .then((result)=>{
    //yak yak yak
  });

Where closing braces of the resolve/reject function are inline with .then.

Of course, this can be simplified a bit by dropping the optional braces, although this might be discouraged by ESLint:

somePromise()
  .then(result =>
    return someOtherPromise()
  )
  .then(result => {
    //yak yak yak
  });

Or with anonymous functions instead of arrow functions:

somePromise()
  .then(function(result) {
    return someOtherPromise()
  })
  .then(function(result) {
    //yak yak yak
  });

To improve readability, we can use named functions instead of anonymous functions to simplify things even further:

doSomething()
    .then((firstResult) => doSomethingElse(firstResult))
    .then((secondResult) => doThirdThing(secondResult))
    .then((finalResult) => console.log("Got the final result: " + finalResult))
    .catch(failureCallback);

A version of the above with anonymous functions would typically look like this:

doSomething()
    .then(function (firstResult) {
        "use strict";
        return doSomethingElse(firstResult);
    }).then(function (secondResult) {
        "use strict";
        doThirdThing(secondResult);
    }).then(function (finalResult) {
        "use strict";
        console.log("Got the final result: " + finalResult);
    }).catch(failureCallback);

Error Handling

Given the following piece of code:

doRemoteCall = function(url) {
  return new Promise((resolve,reject)=>{
    ... if it succeeds
  resolve(data);
    ... if it fails
  reject('it broke');
  });
}

Now, a simple improvement is to use Error objects instead of string literals:

doRemoteCall = function(url) {
  return new Promise((resolve,reject)=>{
    ... if it succeeds
  resolve(data);
    ... if it fails
  reject( new Error('it broke'));

  });
}

This is justified as a better convention, but what are the actual benefits? The only one that comes to my mind is that the receiver of the error can have slightly more information about what went wrong, and so can make better decisions about how to handle it.

executeRemoteCall('http://localhost:8000')
  .then((data)=>{
    //handle success
  })
  .catch((error)=>{
    if(error instanceof TypeError) {
      //handle type error
    } else if (error instanceof ReferenceError) {
      //handle reference error
    }
  // etc etc
});

This approach also makes it compatible with throwing the error once it’s received, although I’m not sure what the benefit would be of doing this.  One possible reason to always provide an Error object as the argument to reject (as opposed to a String or Number literal) is to simplify the logic in the calling code’s catch function. Errors in a typical promise chain can arise from the promise itself, but also in the then handler code.

Consider the following example:

doSomething()
    .then((firstResult) => {
        if (isNotToMyLiking(firstResult)) {
            throw new Error("Cause I feel Like it");
        } else {
			return "String literal";
		}
    })
    .then((secondResult) => doThirdThing(secondResult))
    .then((finalResult) => console.log("Got the final result: " + finalResult))
    .catch((error) => {
		// handle errors
		... 
    });

The only valid actions inside a then handler are to:

  1. return a new promise.
  2. return a synchronous value.
  3. throw a synchronous error.

Actions 1 and 2 will result in the next link in the promise chain being invoked. Action 3 will result in the catch block being invoked. So, from this perspective, the Promises themselves should probably use Errors rather than literals, thereby making things more consistent for the calling code’s catch handler. Not to mention, the Error object is the built-in way to communicate problems in Javascript.

Promises in a Layered Architecture

Imagine we have two modules A and B, where A invokes B to perform some underlying action (for example, A manages some group of objects, and B is our interface to a database)

A->B

If the interface for both A and B use Promises, then a simple implementation of the modules could be:

//B.js

export default queryObject = function (key) {
  return new Promise((resolve, reject)=>{
    //save the data
    //if it goes well
      resolve(data);

    //otherwise
      reject(new Error('could not contact database'));
  });
}


//A.js
export default getModel = function(name) {
  return B.queryObject(name)
}

This seems to me like it is potentially leaking specifics of B that the consumers of A need not know. Something like this could potentially encapsulate these concerns:

//B.js
// unchanged

//A.js
export default getModel = function(name) {
  return new Promise((resolve, reject)=>{
    B.queryObject(name)
    .then(data => resolve(data))
    .catch(error => reject(new Error('could not find model')))
  });
}

This would add another layer of a synchronicity to the process, but would seem to do a better job of data hiding.

Promises on a list of objects

The simple patter of Promise/then/catch is pretty easy to apply for single asynchronous calls, but falls down when you have to manage a list of calls:

saveAllObjects = function(objects) {
  for all objects {
    new Promise((resolve,reject)=>{
      //do something asynchronous
    })
    .then((data)=>{
    //return the data?
    });
  }
}

A nicer way to achieve this would be to use map and Promise.all. This will return a promise which is only fulfilled when all the promises passed to it are fulfilled, and rejects when one of the input promises is rejected. By mapping over the array of objects we want to save, we can easily create the needed array of input promises.

saveAllObjects = function(objects) {
  Promise.all(
    objects.map(
      object => return new Promise((response,reject) => {
        //do something asynchronous
      }
    )
  ))
  .then((data)=>{
    //data is an array of all the resolved objects from the array of promises
  })
  .catch(error => //etc);
}

This gist provides an example of using Promise.all and executing all promises passed in, skipping over the ones which reject –
https://gist.github.com/nhagen/a1d36b39977822c224b8


References

https://gist.github.com/nhagen/a1d36b39977822c224b8
https://www.nczonline.net/blog/2009/03/10/the-art-of-throwing-javascript-errors-part-2/

https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

It's only fair to share...
Share on Facebook
Facebook
Google+
Google+
Tweet about this on Twitter
Twitter
Share on LinkedIn
Linkedin