Search code examples
javascriptjqueryasynchronouspromisedeferred

jQuery deferred: Nesting promises in methods, last callback too early


I have a problem with deferreds/promises. I have a main function that calls two functions that have to be executed serially. The first one contains a bunch of asynchronous web service calls that have to be executed serially first. But actually my second function is called before every asynchronous feedback in the first function is there.

I built a small example that shows my problem: https://fiddle.jshell.net/qLf1c0uq/

Here is the code for reference:

function first() {
  $('ul').append("<li>first started</li>");
  let deferred = $.Deferred();
  setTimeout(function() { // Any async function.
    $('ul').append("<li>first ended</li>");
    deferred.resolve();
  }, 2000);
  return deferred.promise();
}

function second(da) {
  $('ul').append("<li>second started</li>");
  let deferred = $.Deferred();
  $('ul').append("<li>second ended</li>");
  deferred.resolve();
  return deferred.promise();
}

function third(da) {
  $('ul').append('<li>third started</li>')
  let deferred = $.Deferred();
  setTimeout(function() {
    $('ul').append("<li>third ended</li>");
    deferred.resolve();
  }, 2000);

  return deferred.promise();
}

function GroupFunction1() {
  let deferred = $.Deferred();
  $('ul').append("<li>Group 1 started</li>");
  var data = "test2";
  $.when(first()).then(function() {
    second(data);
  }).then(function() {
    third("test2");
  }).then(function() {
    $('ul').append("<li>Group 1 ended</li>");
    deferred.resolve();
  });

  return deferred.promise();
}

function GroupFunction2() {
  $('ul').append("<li>Group 2 started</li>");

  $('ul').append("<li>Group 2 ended</li>");
}

$(function() {
  $.when(GroupFunction1()).then(GroupFunction2);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul></ul>

The GroupFunction2 is started before the third function ends. I was expecting it vice versa.

Anyone can help?

Thanks!


Solution

  • You need to return the promises inside your callback functions. That's the only way the promise chain can wait on them.

    Also, you're abusing the Explicit Promise Creation Antipattern.

    .then() returns a promise. You can just return that. And there's no point in passing a single promise into $.when() because all $.when() is going to do is spit it right back out:

    function first() {
      $('ul').append("<li>first started</li>");
      let deferred = $.Deferred();
      setTimeout(function() { // Any async function.
        $('ul').append("<li>first ended</li>");
        deferred.resolve();
      }, 2000);
      return deferred.promise();
    }
    
    function second(da) {
      $('ul').append("<li>second started</li>");
      let deferred = $.Deferred();
      $('ul').append("<li>second ended</li>");
      deferred.resolve();
      return deferred.promise();
    }
    
    function third(da) {
      $('ul').append('<li>third started</li>')
      let deferred = $.Deferred();
      setTimeout(function() {
        $('ul').append("<li>third ended</li>");
        deferred.resolve();
      }, 2000);
    
      return deferred.promise();
    }
    
    function GroupFunction1() {
      let deferred = $.Deferred();
      $('ul').append("<li>Group 1 started</li>");
      var data = "test2";
    
      // v-- here
      return first()      // v-- here
        .then(function() { return second(data); })
        .then(function() { return third("test2"); })
        .then(function() { $('ul').append("<li>Group 1 ended</li>"); });
    }
    
    function GroupFunction2() {
      $('ul').append("<li>Group 2 started</li>");
    
      $('ul').append("<li>Group 2 ended</li>");
    }
    
    $(function() {
      GroupFunction1().then(GroupFunction2);
    })
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <ul></ul>