Sylvain Leroux Sylvain Leroux - 2 months ago 16
Node.js Question

How to normalize error objects in a promise chain?

I want to chain calls to different libraries using promises. In case of failure, library methods return an object describing the error, but with different fields depending the library.

In order to report any error consistently to the callee, I would like to normalize all error objects to follow a common format. But I have no idea how to do that in an elegant way using Bluebird and/or the standard promise API.

In pseudo-js here is what I have :

var methodAFromLibX_Async = Promise.promisify(...);
var methodBFromLibY_Async = Promise.promisify(...);

methodAFromLibX_Async(...)
.then(function(result) {
methodBFromLibY_Async(...)
.then(function(result) { ... })
.catch(normalizeAndSendErrorFromLibY);
})
.catch(normalizeAndSendErrorFromLibX);


The above code seems to work, but :


  1. I have redundant code between
    normalizeAndSendErrorFromLibY
    and
    normalizeAndSendErrorFromLibX

  2. I my real use case I have to chain more than 2 calls, and the pyramid shape of the code definitely starts looking like a callback hell...






EDIT: In order to be a little more clear, here the solution I envision, but can't achieve :
chain with parallel path for errors & ok results

Answer

The solution you are looking for is

return methodAFromLibX_Async(…)
.then(function(result) {
     return methodBFromLibY_Async(…)
     .then(function(result) {
          return methodCFromLibX_Async(…)
          .catch(normalizeAndThrowErrorFromLibX);
     }, normalizeAndThrowErrorFromLibY);
}, normalizeAndThrowErrorFromLibX)
.then(reportSuccess, reportError);

But this is pretty ugly. Given that your error handlers rethrow the error anyway, you might also use

return methodAFromLibX_Async(…)
.catch(normalizeAndThrowErrorFromLibX)
.then(function(result) {
     return methodBFromLibY_Async(…)
     .catch(normalizeAndThrowErrorFromLibY)
     .then(function(result) {
          return methodCFromLibX_Async(…)
          .catch(normalizeAndThrowErrorFromLibX);
     });
})
.then(reportSuccess, reportError);

which still isn't optimal. You don't want to put a .catch(normalise) on every call to these functions, and you don't want to be forced to nest them. So better factor each of them in their own function:

function withRejectionHandler(fn, normalise) {
     return function() {
         return fn.apply(this, arguments).catch(normalise);
     };
}
var methodA = withRejectionHandler(methodAFromLibX_Async, normalizeAndThrowErrorFromLibX);
var methodB = withRejectionHandler(methodBFromLibY_Async, normalizeAndThrowErrorFromLibY);
var methodA = withRejectionHandler(methodCFromLibX_Async, normalizeAndThrowErrorFromLibX);

return methodA(…).then(methodB).then(methodC).then(reportSuccess, reportError);

You might combine that with the promisification of the library methods.

Comments