Based upon the question here: jQuery chaining and cascading then's and when's and the accepted answer.
I want to break the promise chain at a point, but haven't yet found the correct way. There are multiple posts about this, but I am still lost.
Taking the example code from the original question:
Menus.getCantinas().then(function(cantinas){ // `then` is how we chain promises
Menus.cantinas = cantinas;
// if we need to aggregate more than one promise, we `$.when`
return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas));
}).then(function(meals, sides){ // in jQuery `then` can take multiple arguments
Menus.sides = sides; // we can fill closure arguments here
Menus.meals = meals;
return Menus.getAdditives(meals, sides); // again we chain
}).then(function(additives){
Menus.additives = additives;
return Menus; // we can also return non promises and chain on them if we want
}).done(function(){ // done terminates a chain generally.
// edit HTML here
});
How would I break the chain if cantinas.length == 0
? I would not want to get the meals, neither the additives, frankly I would want to call some kind of "empty result" callback. I have tried the following which is very ugly (but works...). Teach me the the correct way. This still is a valid result, so not a "fail" per se, just empty result I would say.
var emptyResult = false;
Menus.getCantinas().then(function(cantinas){
Menus.cantinas = cantinas;
if (cantinas.length == 0) {
emptyResult = true;
return "emptyResult"; //unuglify me
}
return $.when(Menus.getMeals(cantinas), Menus.getSides(cantinas));
}).then(function(meals, sides){
if (meals == "emptyResult") return meals; //look at my ugliness...
Menus.sides = sides;
Menus.meals = meals;
return Menus.getAdditives(meals, sides);
}).then(function(additives){
if (additives == "emptyResult") return additives;
Menus.additives = additives;
return Menus;
}).done(function(){
if (emptyResult)
//do empty result stuff
else
// normal stuff
});
Sounds like you want to branch, not to break - you want to continue as usual to the
done
. A nice property of promises is that they don't only chain, but also can be nested and unnested without restrictions. In your case, you can just put the part of the chain that you want to "break" away inside yourif
-statement:Firstly, I think it better to say you are seeking to "bypass" (part of) the promise chain rather than to "break" it.
As you say, testing for "emptyResult" in several places is pretty ugly. Fortunately, a more elegant mechanism is available while adhering to the same general principle of not executing some of the promise chain.
An alternative mechanism is to use promise rejection to control the flow, then to re-detect the specific error condition(s) later in the chain, and put it back on the success path.
On the plus side, I find the lack of nesting makes for better readability of the natural success path. Also, for me at least, this pattern would require minimal mental juggling to accommodate further bypasses, if needed.
On the down side, this pattern is slightly less efficient than Bergi's. Whereas the main path has the same number of promises as Bergi's, the
cantinas.length == 0
path requires one more (or one per bypass if multiple bypasses were coded). Also, this pattern requires reliable re-detection of specific error condition(s) - hence theerrMessages
object - which some may find detracts.For folks using built-in browser promises and looking for a way to halt the promise chain without making all consumers know about the rejection case, triggering any chained
then
's orcatch
es or throwing anyUncaught (in promise)
errors, you can use the following:Basically, noopPromise is a basic stubbed out promise interface that takes chaining functions, but never executes any. This relies on the fact that apparently the browser uses duck-typing to determine if something is a promise, so YMMV (I tested this in Chrome 57.0.2987.98), but if that becomes a problem you could probably create an actual promise instance and neuter its then and catch methods.