-2

I'm looking for a little advice regarding the best way to execute multiple ajax calls and then combine the results. My problem is that according to the documentation of the when function it will map the resulting argument differently when there are multiple arguments vs just a single argument. https://api.jquery.com/jquery.when/

This resulted in code that looks like the following. The check seems ugly to me and as far as I can tell would need to be done everytime $.when.apply is used. Is there a way to pass in an array of deferreds to the when function so that the output is of a consistent shape?

//Returns an array of promises that are simple ajax get requests
var getEventFramesPromises = self.webServiceAdapter.getEventFrames(distinctEventFramePaths);
$.when.apply($, getEventFramesPromises).then(function () {
            var eventFrames;
            //Now "arguments" will either be an array with three arguments it the length of the getEventFramesPromises ===1
            if (getEventFramesPromises.length === 1) {
                eventFrames = [arguments[0]];
            } else {
            //Or it will be an array of arrays where each item in the array represents a deferred result
                eventFrames = _.map(arguments, function (args) {
                    return args[0];
                });
            }});
Jason Turan
  • 1,302
  • 12
  • 20
  • Yes, that's exactly what should occur according to the documentation. You can use `.then()` on the `.when()` result to morph the result into a consistent structure. No, there's no way around this *while using jQuery's deferred system*. That's just how it works. – Kevin B Dec 15 '17 at 17:18
  • Yep. `$.when()` has to be the absolutely worst designed API in the whole jQuery lot. I don't use it any more. I use `Promise.all()`. – jfriend00 Dec 15 '17 at 18:36

2 Answers2

2

jQuery $.when() is probably the worst designed API in the jQuery set. Not only does it return a different data type based on what you pass it, but it also doesn't even accept an array as an argument which is the most common and flexible way to use it. What I'd suggest is a simple wrapper that "fixes" the design to be consistent:

// Takes an array of promises and always returns an array of results, even if only one result
$.all = function(promises) {
    if (!Array.isArray(promises)) {
        throw new Error("$.all() must be passed an array of promises");
    }
    return $.when.apply($, promises).then(function () {
        // if single argument was expanded into multiple arguments, then put it back into an array
        // for consistency
        var args = Array.prototype.slice.call(arguments, 0);
        if (promises.length === 1 && arguments.length > 1) {
            // put arguments into an array for consistency
            return [args];
        } else {
            return args;
        }
    });
};

And, here's a slightly different implementation that also takes the three element array that $.ajax() resolves to and makes it just be the single data value so your resolved value is just a simple array of single element results:

// jQuery replacement for $.when() that works more like Promise.all()
// Takes an array of promises and always returns an array of results, even if only one result
$.all = function (promises) {
    if (!Array.isArray(promises)) {
        throw new Error("$.all() must be passed an array of promises");
    }
    return $.when.apply($, promises).then(function () {
        // if single argument was expanded into multiple arguments, then put it back into an array
        // for consistency
        var args = Array.prototype.slice.call(arguments, 0);
        var returnVal;
        if (promises.length === 1 && arguments.length > 1) {
            // put arguments into an array for consistency
            returnVal = [args];
        } else {
            returnVal = args;
        }
        // now make Ajax results easier to use by making it be just an array of results, not an array of arrays
        // 
        return returnVal.map(function(item) {
            // see if this looks like the array of three values that jQuery.ajax() resolves to
            if (Array.isArray(item) && item.length === 3 && typeof item[2] === "object" && typeof item[2].done === "function") {
                // just the data
                return item[0];
            } else {
                return item;
            }
        });
    });
};
jfriend00
  • 683,504
  • 96
  • 985
  • 979
-1

Not sure about why this question is getting down-voted. I think the question is reasonable and I point to the exact documentation in question. So I ended up taking Kevin's advice and wrote a quick helper function that will extract the results of the jquery calls taking into account the behavior when there is only a single value in the array.

function extractValuesFromPromises() { 
        if (arguments.length === 3 && typeof arguments[2].then === 'function') {
            return [arguments[0]];
        } else {
            return _.map(arguments, function (args) {
                return args[0];
            });
        } 
    }

Then in your code you can do this....

var getEventFramesPromises = self.webServiceAdapter.getEventFrames(distinctEventFramePaths);
        $.when.apply($, getEventFramesPromises)
            .then(extractValuesFromPromises)
            .then(function (eventFrames) {
                //Do whatever with ajax results
});
Jason Turan
  • 1,302
  • 12
  • 20