Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with 1 or more jQuery promises

Tags:

I'm making either 1 or more REST/ajax calls to validate some user information. The rest calls are working well and the information is coming back. The issue I'm facing isn't with that part of the code, which looks something like this.

function ensureUsers(recipients){
  var promises = [];
  for(var key in recipients){
      var payload = {'property':recipients[key]};
      promises.push( $.ajax({...}));
  }
  return $.when.apply($,promises);    
}

....

ensureUsers(users) // users is an array of 1 or more users
  .done(function(){
     console.log(arguments);
  )}

If there is more than one user in the initial array, then the arguments in my .done code are structured like this:

[[Object,"success",Object],[Object,"success",Object]...]

I can then iterate over each result, check the status, and proceed.

However if there is only one user in the initial array then .done gets arguments like this:

[Object,"success",Object]

It seems strange to me that the structure of what is returned would change like that. I couldn't find anything about this specific a problem, so I hacked together a solution

var promises = Array.prototype.slice.call(arguments);
if(!Array.isArray(promises[0])){
    promises = [promises];  
}

Is that really the best I can hope for? Or is there some better way to deal with the returned promises from 1 or more ajax calls in jQuery?

like image 737
Rothrock Avatar asked Oct 19 '16 23:10

Rothrock


2 Answers

It seems strange to me that the structure of what is returned would change like that.

Yes, jQuery is horribly inconsistent here. When you pass a single argument to $.when, it tries to cast it to a promise, when you pass multiple ones it suddenly tries to wait for all of them and combine their results. Now throw in that jQuery promises can resolve with multiple values (arguments), and add a special case for that.

So there are two solutions I could recommend:

  • Drop $.when completely and just use Promise.all instead of it:

    var promises = [];
    for (var p of recipients) {
        …
        promises.push( $.ajax({…}));
    }
    
    Promise.all(promises)
    .then(function(results) {
         console.log(results);
    })
    
  • Make each promise resolve with only a single value (unlike $.ajax() that resolves with 3) so that they don't get wrapped in an array, and $.when will produce consistent results regardless of number of arguments:

    var promises = [];
    for (var p of recipients) {
        …
        promises.push( $.ajax({…}).then(function(data, textStatus, jqXHR) {
            return data;
        }) );
    }
    
    $.when.apply($, promises)
    .then(function() {
         console.log(arguments);
    })
    
like image 74
Bergi Avatar answered Sep 25 '22 16:09

Bergi


It appears this functionality is working as currently documented. When you have multiple deferreds passed to $.when it creates a new Promise object that is resolved with the results of each of the results of the passed in deferreds. When you only pass in one, it returns the deferred that you passed in, thus only returning the result instead of an array of results.

I'm not sure if it is any better than your current solution, but you could force it to always have multiple deferreds by having a "fake" deferred that you skip when evaluating the results.

I.E.

function ensureUsers(recipients){
  var promises = [$.Deferred().resolve()];
  for(var key in recipients){
    var payload = {'property':recipients[key]};
    promises.push( $.ajax({...}));
  }
  return $.when.apply($,promises);    
}

You could also potentially make it so the placeholder deferred is resolved with the same structure as what you expect in your real results so it would just appear that the first response is always a success.

like image 23
rdubya Avatar answered Sep 23 '22 16:09

rdubya