سؤال

I have code that requires waiting on a variable number of server responses along with loading several javascript modules via requireJS.

I want to leverage jQuery's when.apply. Unfortunately, the .then() portion of my code is always executing before ANY of my callbacks do.

Here is a summary of my code:

// 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() {
    ...
}

I'm worried that because my async functions are wrapped, jQuery is simply waiting until those functions end, which is immediate. I cannot pass jquery the naked async functions, they have to be wrapped. What do I have to do in order for my functions to be considered 'deferred objects'?

هل كانت مفيدة؟

المحلول

Edit - now that you've disclosed the actual code:

You are not using $.Deferred() correctly.

A typical usage in your context would look like this:

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
});

Then, when all deferred objects that were put into the tasks array get resolved (by you calling .resolve()), then $.when() will fire.

You do not generally pass a callback to $.Deferred() like you were doing (that's used for something else and only used in special circumstances where you want to modify the deferred object in certain ways).

You MUST resolve or reject each deferred yourself by calling one of the methods on the deferred object that resolves or rejects it (there are four different methods that can be used).

Also, note that this structure runs ALL the async tasks in parallel (e.g. it fires them all at the beginning and then notifies you at the end when all of them have finished). You would need a different structure if you want them to run sequentially where you don't start the second one until the first has finished and so on.


If you want async behavior, then $.when() expects its arguments to be jQuery deferred or promise objects. So, for the structure of your code to work, the return from myAsyncWrapper() must be a jQuery deferred or promise object. That way, $.when() can monitor the promise objects for completion and when they are all complete, call your .then() handler.

Then, your async operations must either resolve or reject every one of the deferred objects that you passed to $.when() because it will only call its own .done() when ALL the deferred objects you passed to it have been resolved. Or, it will call its own .fail() if any of the deferred objects you passed to it is rejected.

If you are sure all the arguments you are passing to $.when() are deferreds or promises and your .then() handler is still getting called immediately, then your deferreds must already be resolved somehow because that's the likely explanation for why your .then() handler is called immediately.

If none of the arguments passed to $.when() are deferred or promise objects, then it will resolve itself immediately and thus call your .then() handler immediately which sounds like the behavior you are experiencing.


If your async operations are ajax calls, then jQuery.ajax() already returns a promise object that will be resolved when the ajax call completes. You can directly add the return result from jQuery.ajax() to your tasks array and pass that to $.when() and it will support your asynchronous behavior.

Using jQuery.ajax(), here's the concept for using $.when() with multiple concurrent ajax calls:

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
});

Obviously, you don't have to use all three operations .done(), .notify() and .fail() or you can specify all of them in .then() - I just included all three with comments for educational purposes.


If you want help with some other type of async operation that doesn't already create its own promise object, then I'd be happy to help if you show and describe the actual operation. Deferreds are confusing to get a handle on initially, but like many things are actually quite simple and extremely useful once it finally sinks in.

نصائح أخرى

There is an alternative I found for processing a loop of parallel promises without using an array. Instead use the pattern promise = $.when(promise, anotherPromise).

e.g. for you example something like this

// 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
});

This example assumes you change getLayerInfoFromServer to return a promise:

Notes:

  • The downside of this technique is that the return values of the individual promises are nested within each other, in reverse order, so are not very useful. It is great if you just need to know when it has completed all tasks.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top