javascriptjquerycallback

Wait till a Function with animations is finished until running another Function


I'm having an issue with normal (non-ajax) functions that involve lots of animations within each of them. Currently I simply have a setTimeout between functions, but this isn't perfect since no browsers / computers are the same.

Additional Note: They both have separate animations/etc that collide.

I can't simply put one in the callback function of another

// multiple dom animations / etc
FunctionOne();

// What I -was- doing to wait till running the next function filled
// with animations, etc

setTimeout(function () { 
    FunctionTwo(); // other dom animations (some triggering on previous ones)
}, 1000); 

Is there anyway in js/jQuery to have:

// Pseudo-code
-do FunctionOne()
-when finished :: run -> FunctionTwo()

I know about $.when() & $.done(), but those are for AJAX...


jQuery has an exposed variable (that for some reason isn't listed anywhere in the jQuery docs) called $.timers, which holds the array of animations currently taking place.

function animationsTest (callback) {
    // Test if ANY/ALL page animations are currently active

    var testAnimationInterval = setInterval(function () {
        if (! $.timers.length) { // any page animations finished
            clearInterval(testAnimationInterval);
            callback();
        }
    }, 25);
};

Basic useage:

// run some function with animations etc    
functionWithAnimations();

animationsTest(function () { // <-- this will run once all the above animations are finished

    // your callback (things to do after all animations are done)
    runNextAnimations();

});

Solution

  • You can use jQuery's $.Deferred

    var FunctionOne = function () {
      // create a deferred object
      var r = $.Deferred();
    
      // do whatever you want (e.g. ajax/animations other asyc tasks)
    
      setTimeout(function () {
        // and call `resolve` on the deferred object, once you're done
        r.resolve();
      }, 2500);
    
      // return the deferred object
      return r;
    };
    
    // define FunctionTwo as needed
    var FunctionTwo = function () {
      console.log('FunctionTwo');
    };
    
    // call FunctionOne and use the `done` method
    // with `FunctionTwo` as it's parameter
    FunctionOne().done(FunctionTwo);
    

    you could also pack multiple deferreds together:

    var FunctionOne = function () {
      var
        a = $.Deferred(),
        b = $.Deferred();
    
      // some fake asyc task
      setTimeout(function () {
        console.log('a done');
        a.resolve();
      }, Math.random() * 4000);
    
      // some other fake asyc task
      setTimeout(function () {
        console.log('b done');
        b.resolve();
      }, Math.random() * 4000);
    
      return $.Deferred(function (def) {
        $.when(a, b).done(function () {
          def.resolve();
        });
      });
    };
    

    http://jsfiddle.net/p22dK/