Piotr Berebecki Piotr Berebecki - 7 months ago 8
Javascript Question

How to account for errors in the ajax calls when using alternative promises

I've developed the code below (also available in this codepen http://codepen.io/PiotrBerebecki/pen/QNVEbP) when building an app that will provide weather data depending on user's location coordinates.

User's location will be delivered by the

getCoordinatesMethod1()
function. However if this fails, the location should be delivered by the
getCoordinatesMethod2()
function.

How can I add the
getCoordinatesMethod2()
to the the promises chain (at the bottom) so that it only is used when
getCoordinatesMethod1()
fails to deliver the location?

Also, in future in order to make the app more robust I may add further methods to fetch location data so the chain logic should be able to accommodate that as well.

// The first method to get user location coordinates.
function getCoordinatesMethod1() {
return $.ajax('http://wwww.example.com/coordinates-method-1.json');
}

// The second method to get user location coordinates.
// This should only be used if method 1 fails to deliver coordinates.
function getCoordinatesMethod2() {
return $.ajax('http://wwww.example.com/coordinates-method-2.json');
}

// Function which provides weather data depending on user coordinates.
function getCurrentWeather(latitude, longitude) {
return $.ajax('http://www.example.com/lat=' + latitude + '&lon=' + longitude + 'weather.json');
}

// Promises chain
getUserCoordinates1().then(function(locationData) {
return getCurrentWeather(locationData);
}).then(function(weatherData) {
return showCurrentWeather(weatherData);
})

Answer

You can create a function that calls the first method and, if the returns promise rejects, then calls the second method:

function getCoordinateBoth() {
   return getCoordinatesMethod1().then(null, getCoordinatesMethod2);
}

If there are actually arguments passed to these functions, then you can pass those arguments on like this:

function getCoordinateBoth(/* args here */) {
   var args = Array.prototype.slice.call(arguments);
   return getCoordinatesMethod1.apply(null, args).then(null, function() {
       return getCoordinatesMethod2.apply(null, args)    
   });
}

You could even just have these functions in an array so it is infinitely extensible:

// Put list of functions to be call as a chain until one succeeds
// You can just add more functions here as long as each returns a promise
//    and expects the same arguments
var getCoordFunctions = [getCoordinatesMethod1, getCoordinateMethod2, getCoordinateMethod3];

function getCoordinateChain(/* args here */) {
   var args = Array.prototype.slice.call(arguments);
   var fnArray = getCoordFunctions.slice(0);
   var firstFn = fnArray.shift();
   fnArray.reduce(function(p, fn) {
        return p.then(null, function() {
            return fn.apply(null, args);
        });
   }, firstFn.apply(null, arguments));
}

You could even make this a generic promise chain function:

function chainUntilResolve(array /* other args here */) {
   var args = Array.prototype.slice.call(arguments);
   // toss first arg
   args.shift();
   // make a copy of array of functions
   var fnArray = array.slice(0);
   // remove first function from the array
   var firstFn = fnArray.shift();
   fnArray.reduce(function(p, fn) {
        return p.then(null, function() {
            return fn.apply(null, args);
        });
   }, firstFn.apply(null, arguments));
}


var getCoordFunctions = [getCoordinatesMethod1, getCoordinateMethod2, getCoordinateMethod3];

chainUntilResolve(getCoordFunctions, arg1, arg2).then(function(data) {
    // process data here
}, function(err) {
    // error here
});
Comments