1

I currently have a humongous if-then-else loop in front of me which first gets multiple cantinas from a webservice. Then it gets all available meals (for each menu) and all available side dishes (for each menu). For each of the meals as well as the sides it then checks for all additives and aggregates them.

In the end I have multiple menus with meals, side dishes and all their additives.

The following flowchart shows the process:
enter image description here

I want to beautify the code and want to make use of promises with jQuery - but I can't figure out how to do it as I have to stack a then after a when (at least I think, maybe I have to resolve?). The following is my best try:

//this totally does not work at all, but you get the idea what I want to do
Menus.getCantinas()
       .when(Menus.getMeals(cantinas), Menus.getSides(cantinas)) //when doesn't exist here, neither does cantinas
         .then(Menus.getAdditives(meals, sides) //also throws errors as meals and sides does not exist
           .done(function(cantinas, meals, sides, additives) { //more errors for the people
             Menus.cantinas = cantinas;
             Menus.meals = meals;
             Menus.sides = sides;
             Menus.additives = additives;

             //... some html stuff to build the menus
           });

Make sure to check out the following code snippet for more explanatory code.

window.Menus = {
 cantinas = {},
 meals = {},
 sides = {},
 additives = {},
 callWebservice: function (listname, filter)
    {
        if (filter && filter != '')
            data['$filter'] = filter;

        return jQuery.ajax({
            url: '/_api/web/lists/getbytitle(\'' + listname + '\')/items',
            data: data,
            dataType: 'json'
        });
    },

 getCantinas: function() {
  return Menus.callWebservice("Cantinas", "");
 },
 
 getMeals: function(cantinas) {
  var filterString;
  for (var i = 0; i < cantinas.length; i++) {
   if (i == 0) {
    filterstring = "ID eq " + cantinas[i];
   }
   else {
    filterstring += " or ID eq " + cantinas[i]
   }
  }
  return Menus.callWebservice("Meals", filterString);
 },
 
 
 getSides: function(cantinas) {
  //see above function for filterstuff
  return Menus.callWebservice("Sides", filterString);
 },
 
 getAdditives: function(meals, sides) {
  for (var i = 0; i < meals.length; i++) {
   if (i == 0 && !meals) {
    filterstring = "ID eq " + meals[i];
   }
   else {
    filterstring += " or ID eq " + meals[i]
   }
  }
  
  for (var i = 0; i < sides.length; i++) {
   if (i == 0 && !sides) {
    filterstring = "ID eq " + sides[i];
   }
   else {
    filterstring += " or ID eq " + sides[i]
   }
  }
  return Menus.callWebservice("Additives", "");
 }
 Init: function() {
  //this totally does not work at all, but you get the idea what I want to do
  Menus.getCantinas()
      .when(Menus.getMeals(cantinas), Menus.getSides(cantinas))
        .then(Menus.getAdditives(meals, sides)
          .done(function(cantinas, meals, sides, additives){
         Menus.cantinas = cantinas;
      Menus.meals = meals;
      Menus.sides = sides;
      Menus.additives = additives;
      
      //... some html stuff to build the menus
       });
 }
}
Menus.Init()

I hope I am making sense? How to make use of promises and cascade parameters from one to the next and basically how to make the above statement work and get me my multiple menus with multiple meals, sides and additives.

Dennis G
  • 21,405
  • 19
  • 96
  • 133
  • Are you returning promises from all async operations that are then resolved when the async operation completes? Only promises have the `.then()` method. For example, does `Menus.callWebservice()` return a promise? – jfriend00 Dec 17 '14 at 20:04
  • You can see the shortened code within the code snippet - I'm retuning a jQuery ajax object from `callWebservice` – Dennis G Dec 17 '14 at 20:05
  • 1
    In addition, you can't do something like `.then(Menus.getAdditives(meals, sides))`. You have to pass a function reference like this `.then(function() {return Menus.getAdditives(meals, sides)})`. Only the second form actually waits for the promise. – jfriend00 Dec 17 '14 at 20:05
  • Also, there's no `promise.when()` method in jQuery. There's a `$.when()` which you can wrap in your own function that you pass to a `.then()`. – jfriend00 Dec 17 '14 at 20:08

1 Answers1

5

Like the comments said $.when is a free function. If we use thenable chaining we can get pretty clean syntax here.

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
});
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    simply beautiful. Thanks for the explanation bits! – Dennis G Dec 17 '14 at 20:28
  • 1
    Glad I could help :) Let me know if there is anything else you'd like me to clarify in this example. In general I find that promises model trees of execution very well. – Benjamin Gruenbaum Dec 17 '14 at 20:29
  • Tutorials/documentation don't include returning chains from within functions (like here `return Menus.getAdditives(...`) as well as using `$.when` like that. It makes a lot of sense to me now, just couldn't find an *"advanced"* example like this one anywhere. – Dennis G Dec 17 '14 at 20:31
  • 1
    @DennisG I guess this is mainly because you're using jQuery promises which I personally [**don't appreciate very much**](http://stackoverflow.com/questions/23744612/problems-inherent-to-jquery-deferred). Usually the more advanced tutorial topics are about native ES6 promises or promise libraries like Bluebird or Q. Promises are awesome, not just for aggregation but also for throw safety and logical abstraction of parts - jQuery's promises, while nicer than callbacks lack some desirable properties in my opinion that newer promise implementations have. (inb4 sorry jfriend00 for the rant) – Benjamin Gruenbaum Dec 17 '14 at 20:35
  • Knowledge pr0n. ES6 isn't an option in an IE8 environment. Other libraries might be better, but why another library if jQuery does it a'ight. For my purposes this does the trick, but I will read your linked post to get to know the downsides. – Dennis G Dec 17 '14 at 21:00
  • Bluebird runs on IE8 but yeah I see your point. If your environment is simple enough jQuery deferreds might just cut it :) Happy programming. – Benjamin Gruenbaum Dec 17 '14 at 21:02
  • jQuery done is not same as the terminating chain done :P – Esailija Dec 17 '14 at 23:53
  • @Esailija yes, and as you know we both know that so I don't really get your comment :P You could say though that the when `.done` fires jQuery will use all the error handling logic it has available and that statement holds since it doesn't actually have any :D – Benjamin Gruenbaum Dec 18 '14 at 00:02
  • I think this will have to be: `Menus.sides = sides[0];` and `Menus.meals = meals[0];` (a jQuery weirdness when `$.when()` is used with ajax promises). See the 2nd to last code block here: http://api.jquery.com/jquery.when/ – jfriend00 Dec 18 '14 at 03:39