71

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...


  • MY UPDATED SOLUTION

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

});
Mark Pieszak - Trilon.io
  • 61,391
  • 14
  • 82
  • 96
  • 2
    If `FunctionOne` doesn't has a timeout or anything, you can just call `FunctionOne(); FunctionTwo();`, can't you? – Waleed Khan Aug 24 '12 at 20:55
  • The problem is that they both have separate animations/etc, in different files - etc. So they end up colliding... – Mark Pieszak - Trilon.io Aug 24 '12 at 20:56
  • @arxanas - Yes JavaScript is single threaded, but I suspect he wants to chain two functions together so that one always fires with the other. – Josh Aug 24 '12 at 20:56
  • 3
    `$.when` and `$.done` aren't necessarily just for ajax. If you have various asynchronous tasks in FunctionOne that you want to have finish before firing off FunctionTwo, you can create `Deferred` objects, put them in an array, call `resolve()` on each when the action is done, and then do `$.when.apply($, array).then(function(){...});` – MrOBrian Aug 24 '12 at 21:01
  • 1
    globals are evil, but in this case it _might_ be worth just adding a `isRunning` flag. – ajax333221 Aug 24 '12 at 23:10
  • This very much depends on the content of the functions! – Eric Aug 27 '12 at 10:55
  • 1
    You saved my app, I am eternally grateful – Dagrooms Jun 11 '15 at 16:42

9 Answers9

110

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/

Yoshi
  • 54,081
  • 14
  • 89
  • 103
  • As he said he uses animations, you might want to mention jQuery's `.promise()` method for the fx queue – Bergi Aug 27 '12 at 10:44
  • @Bergi You mean that jQuery returns a deferred object from `animate`? For otherwise I don't really see the need for the promise object here. – Yoshi Aug 27 '12 at 10:50
  • Yes, I did not meant the `Deferred.promise`, but the jQuery method http://api.jquery.com/promise/ – Bergi Aug 27 '12 at 10:53
  • Oh wow just reading all this now Yoshi, good stuff! I'm going to give these a go - tomorrow, and try to mess around with .promise as well. Appreciate it! – Mark Pieszak - Trilon.io Aug 28 '12 at 02:01
  • 1
    Sorry for the delay! I finally got a chance to read more about deferred / done / promise / when, etc. And these are PERFECT! They literally wait till all animations are done on a set thing. when($('whatever')).done() works perfect! – Mark Pieszak - Trilon.io Sep 06 '12 at 14:12
  • ^ you don't need any special object to wait for an animation to complete. Just use $(something).animate({animation}, duration, callback(){}). callback will be executed only when the animation is done. – Saturnix Jul 11 '13 at 09:14
  • The problem is that these functions have tons of animations within themselves, callbacks aren't possible in this situation. @Saturnix – Mark Pieszak - Trilon.io Jul 24 '13 at 02:36
  • Is it really a solution? I think that functionality comes from timeout function usage, not use of done(), am I wrong? If you remove timeout is it still working? – mrarm Jun 13 '14 at 08:46
  • @mrarm the `setTimeout` is just a proof-of-concept. Any other async-method that triggers the `.resolve` method would work. – Yoshi Jun 13 '14 at 09:38
  • I don't get it. When I delete part with timeout() second function is launched before first function ends. I'm trygin it on my code (first function is creating many divs) and i want to launch second after all elements are created. I must dig into it :) – mrarm Jun 13 '14 at 09:40
  • @mram Just open a real question, people will help you then ;) (You could include that you tried this solution but are having problems with adepting it correclty.) – Yoshi Jun 13 '14 at 09:43
  • jQuery's on completes does not work for you? `$('selector').animate({...},OnCompleteFunctionName);` – jave.web Jul 30 '14 at 15:23
  • Thanks a bunch for the multiple Defers note you left... that helped me solve a problem I was having! Cheer brotha. – Cow Oct 23 '14 at 19:10
  • Is there a way to realize it with a recursive function with n-calls? So for example I'm passing an array with n elements and for each element, there will be a recursive call, until the array is empty. – mathew11 Aug 18 '15 at 11:29
  • I think `FunctionOne().done(FunctionTwo);` should be `FunctionOne().done(FunctionTwo());` – Hastig Zusammenstellen Jul 26 '16 at 00:22
  • @HastigZusammenstellen If it were so, `FunctionTwo` would be called before `FunctionOne` and only it's return value would be passed to `.done()`. – Yoshi Jul 26 '16 at 06:42
13

add the following to the end of the first function

return $.Deferred().resolve();

call both functions like so

functionOne().done(functionTwo);
quemeful
  • 9,542
  • 4
  • 60
  • 69
3

Along with Yoshi's answer, I have found another very simple (callback type) solution for animations.

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:

functionOne(); // one with animations

animationsTest(functionTwo);

Hope this helps some people out!

Mark Pieszak - Trilon.io
  • 61,391
  • 14
  • 82
  • 96
2

This answer uses promises, a JavaScript feature of the ECMAScript 6 standard. If your target platform does not support promises, polyfill it with PromiseJs.

You can get the Deferred object jQuery creates for the animation using .promise() on the animation call. Wrapping these Deferreds into ES6 Promises results in much cleaner code than using timers.

You can also use Deferreds directly, but this is generally discouraged because they do not follow the Promises/A+ specification.

The resulting code would look like this:

var p1 = Promise.resolve($('#Content').animate({ opacity: 0.5 }, { duration: 500, queue: false }).promise());
var p2 = Promise.resolve($('#Content').animate({ marginLeft: "-100px" }, { duration: 2000, queue: false }).promise());
Promise.all([p1, p2]).then(function () {
    return $('#Content').animate({ width: 0 }, { duration: 500, queue: false }).promise();
});

Note that the function in Promise.all() returns the promise. This is where magic happens. If in a then call a promise is returned, the next then call will wait for that promise to be resolved before executing.

jQuery uses an animation queue for each element. So animations on the same element are executed synchronously. In this case you wouldn't have to use promises at all!

I have disabled the jQuery animation queue to demonstrate how it would work with promises.

Promise.all() takes an array of promises and creates a new Promise that finishes after all promises in the array finished.

Promise.race() also takes an array of promises, but finishes as soon as the first Promise finished.

Domysee
  • 12,718
  • 10
  • 53
  • 84
1

Is this what you mean man: http://jsfiddle.net/LF75a/

You will have one function fire the next function and so on, i.e. add another function call and then add your functionONe at the bottom of it.

Please lemme know if I missed anything, hope it fits the cause :)

or this: Call a function after previous function is complete

Code:

function hulk()
{
  // do some stuff...
}
function simpsons()
{
  // do some stuff...
  hulk();
}
function thor()
{
  // do some stuff...
  simpsons();
}
Community
  • 1
  • 1
Tats_innit
  • 33,991
  • 10
  • 71
  • 77
1

ECMAScript 6 UPDATE

This uses a new feature of JavaScript called Promises

functionOne().then(functionTwo);

quemeful
  • 9,542
  • 4
  • 60
  • 69
0

You can do it via callback function.

$('a.button').click(function(){
    if (condition == 'true'){
        function1(someVariable, function() {
          function2(someOtherVariable);
        });
    }
    else {
        doThis(someVariable);
    }
});

function function1(param, callback) { ...do stuff callback(); }

Zaheer Babar
  • 1,636
  • 1
  • 15
  • 17
0

Here is a solution for n-calls (recursive function). https://jsfiddle.net/mathew11/5f3mu0f4/7/

function myFunction(array){
var r = $.Deferred();

if(array.length == 0){
    r.resolve();
    return r;
}

var element = array.shift();
// async task 
timer = setTimeout(function(){
    $("a").text($("a").text()+ " " + element);
    var resolving = function(){
        r.resolve();
    }

    myFunction(array).done(resolving);

 }, 500);

return r;
}

//Starting the function
var myArray = ["Hi", "that's", "just", "a", "test"];
var alerting = function (){window.alert("finished!")};
myFunction(myArray).done(alerting);
mathew11
  • 3,382
  • 3
  • 25
  • 32
0

You can use the javascript Promise and async/await to implement a synchronized call of the functions.

Suppose you want to execute n number of functions in a synchronized manner that are stored in an array, here is my solution for that.

async function executeActionQueue(funArray) {
  var length = funArray.length;
  for(var i = 0; i < length; i++) {
    await executeFun(funArray[i]);
  }
};

function executeFun(fun) {
  return new Promise((resolve, reject) => {
    
    // Execute required function here
    
    fun()
      .then((data) => {
        // do required with data 
        resolve(true);
      })
      .catch((error) => {
      // handle error
        resolve(true);
      });
  })
};

executeActionQueue(funArray);
Nidhi Shah
  • 2,338
  • 1
  • 10
  • 14