6

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?

Community
  • 1
  • 1
mnowotka
  • 16,430
  • 18
  • 88
  • 134
  • 3
    Just to add some background information from the last question, The reason the setTimeout is required is resolved deferred objects can't have more progress events bound to them. By delaying the resolve, `.then` is able to bind to the progress event. – Kevin B Apr 05 '13 at 18:23
  • This is quite buggish behaviour if you realise all subtle consequences. – mnowotka Apr 05 '13 at 18:27
  • There's one part of it that i don't quite understand. 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? – Kevin B Apr 05 '13 at 18:28
  • 1
    i could see `.progress` acting just like `.done` and `.fail`, calling the callbacks immediately if the deferred has already indicated some progress. but it does seem there's a bug here: the documentation explicitly says that progress callbacks added to an already-resolved Deferred are still called immediately, but `.then` appears to be breaking that part of the contract. – Eevee Apr 14 '13 at 20:33
  • Like @KevinB, I am far more amazed that .notifies are passed through the chained .thens when resolution is delayed, than that .notifies are not passed through when resolution is immediate. – Beetroot-Beetroot May 07 '13 at 11:28

1 Answers1

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)
Community
  • 1
  • 1
basos
  • 578
  • 4
  • 11