timing issues with jquery deferred

2019-03-20 16:48发布

问题:

This question is carefully distilled version of not asynchronous function executed as jQuery Deferred.

We have 2 jsfiddles:

  1. http://jsfiddle.net/XSDVX/1/ - here the progress event is not fired, despite calling notify() function.

  2. http://jsfiddle.net/UXSbw/1/ - here the progress event is fired as expected.

The only difference is one line of code:

setTimeout(dfd.resolve,1);

versus

dfd.resolve();

Questions are:

  1. How is .then catching the .notify that is called before this callback returns when we delay the resolve? Think about it. .then takes the deferred object that was returned from it's first parameter and creates a new deferred object from it, binding to it's done progress and fail events. If the notify was called before the deferred was returned, how is .then catching it even with a setTimeout? (Thanks to https://stackoverflow.com/users/400654/kevin-b for asking this)

  2. Can I get rid of setTimeout() and still have progress callback fired?

回答1:

Made a big refactor and here is one final working example, with progress monitoring.

Now the important parts.

  • JQuery deferreds do not execute any progress callbacks, after the resolve has been called (with one exception). In your example (without setTimeout), the deferred is immediately resolved, no chances to run progress.
  • Do the hooks of all callbacks, especially the progress ones, before we trigger enything on the final Deferred. This is achieved by passing the final Deferred (now beacon) to the execution function, after we have populated it's triggers.
  • I refactored the API so the func to be executed is deferred agnostic.
  • This solution, uses a closure of a local (to the reduce iterator function) Deferred, inside the memo.then function, in order to continue the execution chain.

EDIT: I forgot your first question. This behavior is achieved via the means of a closure (the dfd variable in the "x" function).

The function "x" returns immediately (after triggering a notify event which now can be processed, as all the Deferreds of the execution chain have been created, and the done, fail, progress hooks of the "executePromiseQueueSync" have been hooked).

Also the function of the setTimeout "closes" the dfd in a closure, so it can access the variable despite that the "x" has returned. The "then" call continues by creating the next deferred that is linked to the first one.

After the JS VM yields (it has not other things to do), the setTimeout triggers it's associated function, that (by the means of closure) have access to the "closed" dfd variable. The Deferred is resolved and the chain can continue.

EDIT2: Here is a refactored version that adds support for long executing, deferred supported functions, where they notify their caller for their progress.

EDIT3: Here is another version, without underscore binding and with a jq-ui progressbar example.

By the way this is very good idea for complex app initialization routines.

Source (of the first version)

function executePromiseQueueSync(queue, beacon){
    var seed = $.Deferred(),
        le = queue.length,
        last;
    beacon.notify(0);
    last = _.reduce(queue, function(memo, ent, ind){
       var df = $.Deferred();
        df.then(function(){
            console.log("DBG proggie");
            beacon.notify((ind+1)/le*100);
        });
        console.log("DBG hook funk "+ind);
        memo.then(function(){
          console.log("DBG exec func "+ind);
          ent.funct.apply(null, ent.argmnt);
          df.resolve();
        });

        return df.promise();
    }, seed.promise());
    last.then(function(){
        beacon.resolve(100)
    });
    seed.resolve(); // trigger

    return beacon.promise();
}

function x(){
    // do stuff
    console.log("blah");
}

var promisesQueue = [],
     beacon = $.Deferred(); 

promisesQueue.push({funct: x, argmnt:[]});
promisesQueue.push({funct: x, argmnt:[]});
promisesQueue.push({funct: x, argmnt:[]});

function monTheProg(pct) 
{
    console.log('progress '+pct);
}

// first hook, then exec
beacon.then(function(){
        console.log('success');
    }, function(){
        console.log('failure');
    }, monTheProg);

// do the dance
executePromiseQueueSync(promisesQueue, beacon)