如何使用jQuery Deferreds数组?

132
我有一个应用程序,需要按特定顺序加载数据:首先是根URL,然后是所有模式,最后使用这些模式和URL来初始化各种数据对象的应用程序。当用户使用应用程序导航时,会加载数据对象、对其进行模式验证并显示它们。当用户进行增删改查操作时,模式提供了第一层验证。
我在初始化方面遇到了问题。我使用Ajax调用来获取根对象,$.when(),然后创建一个承诺数组,每个模式对象对应一个承诺。那是有效的。我在控制台看到了这个获取。
然后我看到了所有模式的获取,所以每个$.ajax()调用都有效。fetchschemas()确实返回一个承诺数组。
但是,最终的 when() 子句从未触发,“DONE”一词从未在控制台上出现。jquery-1.5的源代码似乎暗示“null”作为传递给$.when.apply() 的对象是可以接受的,因为如果没有传递对象,when() 将构建一个内部Deferred()对象来管理列表。
使用 Futures.js 时会起作用。如果不像这样管理 jQuery Deferreds 数组,该怎么办?
    var fetch_schemas, fetch_root;

    fetch_schemas = function(schema_urls) {
        var fetch_one = function(url) {
            return $.ajax({
                url: url,
                data: {},
                contentType: "application/json; charset=utf-8",
                dataType: "json"
            });
        };

        return $.map(schema_urls, fetch_one);
    };

    fetch_root = function() {
        return $.ajax({
            url: BASE_URL,
            data: {},
            contentType: "application/json; charset=utf-8",
            dataType: "json"
        });
    };

    $.when(fetch_root()).then(function(data) {
        var promises = fetch_schemas(data.schema_urls);
        $.when.apply(null, promises).then(function(schemas) {
            console.log("DONE", this, schemas);
        });
    });

我有一个几乎相同的问题,只是在打印“DONE”之前,我需要为fetch_one中的每个ajax查询触发一个“success”方法。你会如何解决这个问题?我尝试在“fetch_one”之后使用.pipe,但似乎没有起作用。 - CambridgeMike
4个回答

198

你正在寻找

$.when.apply($, promises).then(function(schemas) {
     console.log("DONE", this, schemas);
}, function(e) {
     console.log("My ajax failed");
});

这也可以起作用(对于某些工作的价值,它不能修复破碎的ajax):

$.when.apply($, promises).done(function() { ... }).fail(function() { ... });` 

为了使$.when内的this指向jQuery,你需要传递$而不是null。源代码可能不会有影响,但传递null并不优于传递$

通过将所有的$.ajax替换为$.when来模拟它们,这个示例可以正常运行

因此,问题要么出在你的 ajax 请求中,要么出在你传递给 fetch_schemas 的数组中。


谢谢。这个语法和done().fail()有什么不同? - Elf Sternberg
2
@elf Sternberg,.then(a,b) === .done(a).fail(b) 这是一个懒惰的速记法。如果您希望,可以调用 .done(a).fail(b) - Raynos
1
哦,而且使用 $.when.apply($, ...) 和 $.when.apply(null, ...) 似乎是无关紧要的。jQuery本身没有一个promise()方法,所以它被忽略了,而是选择一个内部生成的Deferred对象(jQuery 1.5,第943行)。 - Elf Sternberg
1
@ElfSternberg,确实与此无关,但为了可读性,我不需要再看一眼$.when.apply($, ...null让我感到困惑。这是风格和编码实践的问题。我不得不阅读源代码来确认this在jQuery.when内部不会引发空引用! - Raynos
7
使用null让我想到“好吧,这是某种变通方法”(确实如此),而如果使用$,我的注意力会被分散到思考$的作用是什么。 - Danyal Aytekin
显示剩余3条评论

53

上述的解决方法(谢谢!)并没有完全解决问题,即如何获取提供给延迟对象的resolve()方法的返回值,因为jQuery使用单独的参数调用done()fail()回调函数,而不是一个数组。这意味着我们必须使用arguments伪数组来获取所有由延迟对象数组返回的已解析/拒绝的对象,这很丑陋:

$.when.apply($, promises).then(function() {
     var schemas=arguments; // The array of resolved objects as a pseudo-array
     ...
};

既然我们传入了一个延迟对象数组,那么返回一个结果数组会很好。我们还希望返回一个真正的数组而不是伪数组,这样我们可以使用Array.sort()等方法。

以下解决方案受到when.jswhen.all()方法的启发,解决了这些问题:

// Put somewhere in your scripting environment
if (jQuery.when.all===undefined) {
    jQuery.when.all = function(deferreds) {
        var deferred = new jQuery.Deferred();
        $.when.apply(jQuery, deferreds).then(
            function() {
                deferred.resolve(Array.prototype.slice.call(arguments));
            },
            function() {
                deferred.fail(Array.prototype.slice.call(arguments));
            });

        return deferred;
    }
}

现在你可以简单地传入一个延迟/承诺数组,并在回调函数中得到一个已解决/拒绝对象数组,如下所示:

$.when.all(promises).then(function(schemas) {
     console.log("DONE", this, schemas); // 'schemas' is now an array
}, function(e) {
     console.log("My ajax failed");
});

@crispyduck - 你知道在then()中的“schemas”变量中数组元素的顺序是否总是与when()中的“promises”变量中的ajax调用顺序相同吗? - netpoetica
6
这个功能应该被内置到jQuery中,但是jQuery团队已经多次拒绝了这个请求。与此同时,人们不断在这里提出这个问题,并针对jQuery打开类似的工单,最终导致用户自行实现或使用apply()方法,真是匪夷所思。 - mindplay.dk
谢谢您提供的解决方案!如果有一个或多个失败,是否有办法也获取成功的项目? - doktoreas
你在这里做的就是将 arguments 的操作隐藏到了自己的方法中。这样做很适合重复使用,但并没有解决处理 arguments 的“丑陋”问题(你可以很容易地只写成:var schemas=Array.prototype.slice.call(arguments);) - cowbert
2
@crispyduck,deferred.fail(...) 不应该改成 deferred.reject(...) 吗? - Bob S
显示剩余2条评论

18

如果您正在使用JavaScript的ES6版本,那么有一个扩展运算符(...),可以将对象数组转换为逗号分隔的参数。

$.when(...promises).then(function() {
 var schemas=arguments; 
};

了解更多关于ES6扩展操作符的内容,请在此处查找。


1
是的。虽然我们这些使用Coffeescript或其后代/模仿者的人现在已经拥有了该运算符。 - Elf Sternberg

-1

使用以下代码时进行扩展:

var rawWhen = $.when
$.when = function(promise) {
    if ($.isArray(promise)) {
        var dfd = new jQuery.Deferred()
        rawWhen.apply($, promise).done(function() {
            dfd.resolve(Array.prototype.slice.call(arguments))
        }).fail(function() {
            dfd.reject(Array.prototype.slice.call(arguments))
        })
        return dfd.promise()
    } else {
        return rawWhen.apply($, arguments)
    }
}

这个答案比评分更高的答案好在哪里?它到底是做什么的?没有解释的答案并不是很有帮助。 - Ian Kemp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接