0

I have a single Ajax function which returns an array of objects. I then utilize this array of objects to dictate each of the following child Ajax calls. I append the results of the child Ajax calls to an element created by the parent. I would like to use a Deferred object(s) and call a third function, but only when all Ajax calls are completed in both the child and parent functions.

Where am I going wrong, and is there a more effective implementation to accomplish this functionality?

HTML:

<div class="out"></div>
<button id="display">display</div>
<button id="clear">clear</div>

JavaScript:

$(document).ready(function(){

  var lib = {};

  lib.func3 = function(){

      alert("Success!  Func3 wasn't called until all Ajax calls were completed.")

  }

  lib.func2 = function(elem, userObj){

    var root = 'http://jsonplaceholder.typicode.com';
    return $.ajax({
      url: root + '/posts?userId=' + userObj.id,
      method: 'GET',
      success: function(data){
        var ol = $("<ol/>")
        $(data).each(function(i){
          var elem = $("<li/>").html(data[i].title);  
          $(ol).append(elem);
        });
        elem.append(ol);
      }
    }); 

  }

  lib.func1 = function(){    

    var func1_deferred_obj = $.Deferred();
    var func2_promises = [];

    var root = 'http://jsonplaceholder.typicode.com';
    $.ajax({
      url: root + '/users',
      method: 'GET',
      success: function(data){

        var ol = $("<ol/>");

        $(data).each(function(i){
          var elem = $("<li/>").html(data[i].name);  
          $(ol).append(elem);
          var func2_deferred_obj = lib.func2(elem, data[i])
          func2_promises.push(func2_deferred_obj);
        });

        $(".out").append(ol)

      }
    })

    $.when.apply($, func2_promises).done(function(){func1_deferred_obj.promise()})
    return func1_deferred_obj;
  }

  $("#display").click(function(){

    $(this).unbind("click");

    $.when(lib.func1()).done(function(){
      lib.func3();
      $("#display").bind('click');
    })

  })

  $("#clear").click(function(){ $(".out").empty(); })

});

CODEPEN: http://codepen.io/dclappert/pen/epBLEB?editors=101

David Calvin
  • 194
  • 1
  • 11
  • The `func2_promises` array is filled asynchronously, after you awaited its contents. Nest your handlers here. Oh, and avoid the [deferred antipattern](http://stackoverflow.com/q/23803743/1048572) – Bergi Sep 28 '15 at 15:59
  • I'm confused. Don't I need to append returned promise objects from the child function. Once all of those promise objects are completed (done), then I would return a promise object from the parent function? I was looking up the deferred antipattern and I am working on a way to adapt my current script. – David Calvin Sep 28 '15 at 20:48
  • Yes, you need to put the promises in the array. But the place where you call `$.when` is wrong. Don't make `func2_promises` a "global" variable, instead try putting it in the scope of the ajax callback (which should become `then`, not `success`) – Bergi Sep 28 '15 at 21:00
  • 1
    I made a few changes and it appears to exhibit the correct functionality. I'm just wonder the design still mirrors the deferred antipattern? http://codepen.io/dclappert/pen/WQoYrJ?editors=101 – David Calvin Sep 29 '15 at 15:35
  • Perfect! No deferreds in your code, no deferred antipattern. Pure promise composition and `then` everywhere. You may even [post that code as an answer](http://stackoverflow.com/help/self-answer). – Bergi Sep 29 '15 at 15:51
  • Awesome! I have one more question. Say I want to add another Ajax call, prior to the looping calls. So three total calls, one single call, then a call with child Ajax function. Would I follow the convention: $.when(func1).then(func2).done(success()) ? When I attempt this pattern it breaks and success() is called before func2 (with the looping Ajax child function) returns its promise. – David Calvin Sep 29 '15 at 16:12
  • Hm, nah, that should work: `$.when().then(func1).then(func2).then(…)` (or `func1().then(func2).…`) You just have to make sure not to use `.then(func2())`, but to pass a *function* that returns a promise to `then`. – Bergi Sep 29 '15 at 16:40
  • Is there a way to pass parameters via `then()` to the called function? I tried: `$.when(func4()).done(func1()).then().done(func3())` as to enable the passing of parameters. This doesn't seem to exhibit the same functionality as `func4().then(func1).then(func3)`. I need to be able to pass parameters in the calling function. Thank you for all your help. I `promise()` this is my last question. – David Calvin Sep 29 '15 at 17:30
  • Yes, you must not use `done` but only `then`, and you *must pass functions*. `then(func1)`, not `.done(func1())`. As arguments, the functions will receive the result of the promise. If you have extra parameter, use a function expression: `func1().then(function(func1result) { return func2(func1results, extraArgs, …); }).…` – Bergi Sep 29 '15 at 17:48

0 Answers0