Jquery: $.when behaves differently depending on nu

2019-04-28 10:30发布

问题:

$.when behaves differently depending on whether one or more Deferred object are passed to it. This behaviour is documented in the docs - but the problem is that it is forcing me to write two different code paths.

function foo (dfds) {
    $.when.apply(this, dfds).done(function() {
        console.log(arguments);
    });
}

Case I:

foo([$.getJSON("http://freegeoip.net/json/8.8.8.8"),
     $.getJSON("http://freegeoip.net/json/8.8.8.9")]);
....
/* Output (what I'd come to expect) */
[Array[3], Array[3]]

Case II:

foo([$.getJSON("http://freegeoip.net/json/8.8.8.8")]);
....
/* Output (the original unwrapped deferred's arguments) */
[Object, "success", Object]

Any way to elegantly handle this without resorting to checking the length of dfd or the type of arguments?

回答1:

I don't think you can avoid explicit testing the number of deferred objects. Assuming you want to return the deferred object:

function foo (dfds) {
    if(dfds.length > 1) {
        return $.when.apply(this, dfds);
    }
    else {
        return dfds[0].pipe(function() {
            return [Array.prototype.slice.call(arguments, 0)]
        });
    }
}

You could create a jQuery plugin to wrap this functionality and make it reusable:

(function($) {
    $.when_ = function() {
        if(arguments.length > 1) {
            return $.when.apply(this, arguments);
        }
        else {
            return arguments[0].pipe(function() {
                return [Array.prototype.slice.call(arguments, 0)];
            });
        }
    };
}(jQuery));

You could also override $.when but I don't know for sure whether it is used internally or not.



回答2:

jQuery has a bad habit of messing with arguments logic. In your case, a simple loop would normalize it if you want a callback for each deferred object:

$.each(dfds, function() {
    $.when(this).done(function() {
        console.log(arguments);
    });
});

You can also loop the arguments so you don’t have to send an array:

function foo() {
    $.each(arguments, function() {
        $.when(this).done(function() {
            console.log(arguments);
        });
    });
}

UPDATE

If you always want to return an array of deferred object, you probably need to check the length of arguments in foo like Felix posted, or do something like:

function foo() {
    $.when.apply(this, arguments).done(function() {
        var args = $.makeArray(arguments),
            result = args[0].constructor != Array ? [args] : args;
        console.log(result);
    });
}

http://jsfiddle.net/2ht8d/



回答3:

Just push a dummy object onto the end of your dfds array. That will ensure it always has length 2 or greater, assuming you have at least one deferred.