0

I just started using promises for multiple ajax calls that I will not know how many I need to create. I have found these posts:

jQuery Deferred - waiting for multiple AJAX requests to finish jQuery make dynamic ajax calls and wait for them to complete

So I tried to play with promises and in this simple example:

function doSomething1() {
  var deferred1 = $.Deferred();
  setTimeout(function() {
    deferred1.resolve();
  }, 2000);
  return deferred1.promise();
}

function doSomething2() {
  var deferred2 = $.Deferred();
  setTimeout(function () {
    deferred2.resolve();
  }, 1000);
  return deferred2.promise();
}

var promise = doSomething1();
var promise2 = doSomething2();

function multiplePromises() {
  var promises = [];
  for (var i = 0; i < 3; i++) {
      var deferred = $.Deferred();
      deferred.resolve();
      promises.push(deferred);
  }
  return $.when.apply(undefined, promises).promise();
}


multiplePromises().done(function () {
  console.log("yay");
});

I get "yay" right away. Now to simulate an ajax call, I tried to use setTimeout. Like this:

function multiplePromises2() {
  var promises = [];
  for (var i = 0; i < 3; i++) {
      setTimeout(function () {
      var deferred = $.Deferred();
      deferred.resolve();  
      promises.push(deferred);
      },2000);      
  }
  return $.when.apply(undefined, promises).promise();
}

multiplePromises2().done(function () {console.log("timeout and promises");});

I don't get the actual two seconds delay. If I try this:

function multiplePromises2() {
  var promises = [];
  for (var i = 0; i < 3; i++) {
      var deferred = $.Deferred();
      setTimeout(function () {
      deferred.resolve();  
      },2000);
      promises.push(deferred);
  }
  return $.when.apply(undefined, promises).promise();
}

I never get the message at all that multiplePromises2 was finished. So in my actual ajax call, I was not able to use deferred. I tried doing it like this:

    saveNewProjects(projects) {
        var projectPromises: any[] = [];

        for (var projectName in projects) {
            var project = projects[projectName][0].Project;
            if (project && project.id == 0 && project.name !== null) {
                var deferred = $.Deferred();

                var postProject = $.ajax({
                    url: "projects",
                    contentType: "application/json",
                    type: "POST",
                    data: JSON.stringify(project),
                    crossDomain: $.support.cors,
                    xhrFields: {
                        withCredentials: $.support.cors,
                    },
                    success: function (data) {
                        deferred.resolve(data);
                    },

                });
                projectPromises.push(postProject);
            }
        }
        return $.when.apply($, projectPromises).promise();
    }

I call that function like this:

 this.saveNewProjects(projects).then(() => {

Am I thinking about promises and deferred properly? It doesn't seem to get to my then or done callbacks. Thanks in advance.

Community
  • 1
  • 1
Crystal
  • 28,460
  • 62
  • 219
  • 393
  • You need to think carefully what part of your code is synchronous and what is not (i.e. adding items to list of promises after timeout demonstrate asynchronous call, where you expect synchronosly adding item to list and asynchronosly `resolve`...). – Alexei Levenkov May 10 '14 at 01:35

1 Answers1

3

This is a nasty little problem, but the issue isn't because you're using deferreds incorrectly - because you have the idea right: it's because you have a closure problem.

Let's take this example:

function multiplePromises2() {
    var promises = [];
    for (var i = 0; i < 3; i++) {
        var deferred = $.Deferred();
        setTimeout(function () {
            deferred.resolve();  
        },2000);
        promises.push(deferred);
    }
    return $.when.apply(undefined, promises).promise();
}

This seems innocent enough. But unfortunately you aren't actually defining a new variable deferred for each iteration of the loop. The javascript interpreter moves all variable declarations to the top of the scope. So this is actually identical to:

function multiplePromises2() {
    var promises, deferred;
    promises = [];
    for (var i = 0; i < 3; i++) {
        deferred = $.Deferred();
        setTimeout(function () {
            deferred.resolve();  
        },2000);
        promises.push(deferred);
    }
    return $.when.apply(undefined, promises).promise();
}

Can you see why there may be a problem now? The reference to the variable deferred in your setTimeout actually changes, because you are updating deferred at every iteration. So by the time the 2 seconds is up, you are resolving a single deferred (at promises[2]) three times. To prove this you can add this line: console.log(promises.indexOf(deferred)); inside your setTimeout.

So now that we've identified the problem, it's pretty easy to fix. Just a standard IIFE solution:

function multiplePromises2() {
    var promises, deferred;
    promises = [];
    for (var i = 0; i < 3; i++) {
        deferred = $.Deferred();
        (function(deferred) {
            setTimeout(function () {
                deferred.resolve();  
            },2000);
            promises.push(deferred);
        })(deferred);
    }
    return $.when.apply(undefined, promises).promise();
}
sahbeewah
  • 2,690
  • 1
  • 12
  • 18