jQuery的when.apply和包装异步函数

时间:2014-04-21 22:31:46

标签: javascript jquery asynchronous jquery-deferred

我的代码需要等待可变数量的服务器响应,同时通过requireJS加载几个javascript模块。

我想利用jQuery的when.apply。不幸的是,我的代码的.then()部分总是在我的任何回调之前执行。

以下是我的代码摘要:

// load module func
function loadRequireJsModule(arg, callback) {
    require([arg], callback);
}

// get layer info from server func  
function getLayerInfoFromServer(arg, callback) {
    var layerInfoListener = new DataLayer([arg]);
    layerInfoListener.addRetrievedCallback(callback);
    layerInfoListener.retrieveLayerInfo();
}

tasks.push( $.Deferred( function() {
    loadRequireJsModule( "./" + layerJSON.type, function( module ) {
        layer.controller = module;
    });
}));

for (i=0; i<layerJSON.views.length; i++) {

    layer.views[i] = {};

    // get renderer class from require.js
    tasks.push( $.Deferred( function() {

        loadRequireJsModule( "./impl/" + layerJSON.views[i].renderer, function( index ){
            return function( module ) {
                layer.views[index].renderer = new module();
            }
        }(i));
    }));

    // POST request for layerInfo
    tasks.push( $.Deferred( function() {

        getLayerInfoFromServer( {
            request: "configure",
            layer:  layerJSON.layer,
            configuration: layerJSON.views[i]
        }, function( index ){
            return function( dataLayer, layerInfo ) {
                layer.views[index].dataService = new TileService( layerInfo );
            }
        }(i));
    }));
}

$.when.apply($, tasks).then( function() {
    ...
}

我担心因为我的异步函数被包装,jQuery只是等待这些函数结束,这是立即的。我不能通过jquery传递裸异步函数,它们必须被包装。为了将我的函数视为“延迟对象”,我该怎么做?

2 个答案:

答案 0 :(得分:5)

编辑 - 现在您已经披露了实际代码:

您没有正确使用$.Deferred()

您的上下文中的典型用法如下所示:

var d;
var tasks = [];
for (var i = 0; i < len; i++) {
    // create new deferred object
    d = $.Deferred();
    // put deferred into tasks object
    tasks.push(d);
    loadRequireJsModule( "./impl/" + layerJSON.views[i].renderer, function( index, deferred ){
        return function( module ) {
            layer.views[index].renderer = new module();
            // now that this operation is done, resolve our deferred
            deferred.resolve();
        }
    }(i, d));
}
$.when.apply($, tasks).done(function() {
    // code executes here when all the deferreds 
    // have had `.resolve()` called on them
});

然后,当放入任务数组的所有延迟对象得到解决时(通过调用.resolve()),$.when()将会触发。

您通常不会像往常一样将回调传递给$.Deferred()(用于其他内容,仅用于您希望以某种方式修改延迟对象的特殊情况)。< / p>

你必须通过调用解析或拒绝它的延迟对象上的一个方法来解决或拒绝每个延迟的自己(有四种不同的方法可以使用)。

另外,请注意,此结构并行运行所有异步任务(例如,它在开始时将它们全部触发,然后在所有这些任务完成时通知您)。你需要一个不同的结构,如果你希望它们顺序运行你不会开始第二个结构直到第一个结束,依此类推。


如果您想要异步行为,则$.when()期望其参数为jQuery deferredpromise个对象。因此,要使代码的结构起作用,myAsyncWrapper()的返回必须是jQuery deferredpromise对象。这样,$.when()可以监控promise对象的完成情况,当它们全部完成时,请调用.then()处理程序。

然后,您的异步操作必须解析或拒绝传递给$.when()的每个延迟对象,因为只有当您传递给它的所有延迟对象都有.done()时,它才会调用它自己的.fail()已经解决了。或者,如果您传递给它的任何延迟对象被拒绝,它将调用自己的$.when()

如果您确定要传递给.then()的所有参数都是延迟或承诺,并且您的.then()处理程序仍然被立即调用,那么您的延迟必须已经以某种方式解决,因为这样做&#39;可能解释为什么立即调用$.when()处理程序。

如果传递给deferred的任何参数都不是promise.then()个对象,那么它会立即自行解决,从而立即调用您的jQuery.ajax()处理程序,这听起来像是你正在经历的行为。


如果您的异步操作是ajax调用,那么jQuery.ajax()已经返回一个promise对象,该对象将在ajax调用完成时解析。您可以直接将$.when()的返回结果添加到您的tasks数组中,并将其传递给jQuery.ajax(),它将支持您的异步行为。

使用$.when(),这里有var promises = []; for (var i = 0; i < myNum; i++) { promises.push($.ajax(...)); } $.when.apply($, promises).done(function() { // put code here that runs when all the ajax calls have completed // the results of all the ajax calls are in // arguments[0], arguments[1], etc... }).notify(function() { // .notify is purely optional and only necessary if you want to be notified as // each async operation completes // This will get called as each async operation completes with whatever arguments // were passed to the success handler }).fail(function() { // this will get called immediately if any of the async operations failed // the arguments are the same as the .done() function except that some may be empty // if they didn't complete before one of them failed }); 使用多个并发ajax调用的概念:

.done()

显然,您不必使用所有三项操作.notify().fail().then(),或者您可以在{{1}}中指定所有这些操作 - 我只是包括所有这三个与教育目的的评论。


如果您需要其他类型的异步操作的帮助,而这些操作还没有创建自己的promise对象,那么如果您展示并描述实际操作,我很乐意提供帮助。延迟很容易在最初得到处理,但是很多东西实际上非常简单,一旦它最终沉入其中就非常有用。

答案 1 :(得分:1)

我发现了一种替代方法,可以在不使用数组的情况下处理并行promise的循环。而是使用模式promise = $.when(promise, anotherPromise)

e.g。对于你这样的例子

// Start with an empty resolved promise - undefined does the same thing!
var promise;

for (i = 0; i < layerJSON.views.length; i++) {

    layer.views[i] = {};

    // get renderer class from require.js
    promise = $.when(promise, loadRequireJsModule("./impl/" + layerJSON.views[i].renderer, function (index) {
            return function (module) {
                layer.views[index].renderer = new module();
            }
        }
    ));
}

// Now do something after all the promises are resolved
promise.then(function () {
    // Do the next part
});

此示例假设您更改getLayerInfoFromServer以返回承诺:

说明:

  • 这种技术的缺点是各个承诺的返回值以相反的顺序相互嵌套,因此不是很有用。如果您只需要知道它何时完成了所有任务,那就太棒了。