等待一个带有动画效果的函数执行完毕后再运行另一个函数。

72

我在处理一些包含大量动画的普通(非ajax)函数时遇到了问题。目前,我只是在函数之间使用setTimeout,但这并不完美,因为没有浏览器/计算机是相同的。

另外注意:它们都有不同的动画等,会相互冲突。

我不能简单地将一个函数放在另一个回调函数中。

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

有没有办法在js/jQuery中实现以下功能:

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

我知道$.when()$.done()用于AJAX,但那些只是针对AJAX的。


  • 我的更新解决方案

jQuery有一个暴露的变量(在jQuery文档中没有列出原因不明),叫做$.timers,其中包含当前正在进行的动画数组。

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

基本用法:

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

});

2
如果FunctionOne没有超时或其他问题,你可以直接调用FunctionOne(); FunctionTwo();,对吗? - Waleed Khan
@arxanas - 是的,JavaScript是单线程的,但我怀疑他想将两个函数链接在一起,以便一个总是与另一个一起触发。 - Josh
3
$.when$.done不一定只适用于ajax。如果您在FunctionOne中有各种异步任务需要完成,然后再启动FunctionTwo,您可以创建“Deferred”对象并将它们放入数组中,在每个任务完成时调用resolve(),最后使用$.when.apply($, array).then(function(){...});来处理。 - MrOBrian
1
全局变量是有害的,但在这种情况下,只需添加一个 isRunning 标志可能是值得的。 - ajax333221
1
你拯救了我的应用程序,我永远感激不尽。 - Dagrooms
显示剩余2条评论
9个回答

111

你可以使用jQuery的 $.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);

你也可以将多个deferred打包在一起:

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/


1
抱歉耽擱了!我終於有機會更多地閱讀關於deferred/done/promise/when等方面的內容。這些東西簡直太完美了!它們可以等待所有動畫在一個設定的物件上完成,當使用 $('whatever').done() 時,效果非常好! - Mark Pieszak - Trilon.io
你不需要任何特殊的对象来等待动画完成。只需使用$(something).animate({animation}, duration, callback(){}), 当动画完成时,回调函数将被执行。 - Saturnix
问题在于这些函数本身有大量的动画效果,无法使用回调函数来实现。@Saturnix - Mark Pieszak - Trilon.io
我认为 FunctionOne().done(FunctionTwo); 应该改为 FunctionOne().done(FunctionTwo()); - Hastig Zusammenstellen
如果是这样的话,FunctionTwo将在FunctionOne之前被调用,只有它的返回值才会传递给.done() - Yoshi
显示剩余11条评论

13

将以下内容添加到第一个函数的末尾

return $.Deferred().resolve();

像这样调用两个函数

functionOne().done(functionTwo);

3

除了Yoshi的答案外,我还发现另一个非常简单的(回调类型)动画解决方案。

jQuery有一个公开的变量(出于某种原因未在jQuery文档中列出),称为$.timers,它保存当前正在进行的动画数组。

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

基本用法:

functionOne(); // one with animations

animationsTest(functionTwo);

希望这能帮助一些人!

2

这个答案使用了 JavaScript ECMAScript 6 标准中的 promises 特性。如果你的目标平台不支持 promises,可以使用PromiseJs进行 polyfill。

通过在动画调用上使用 .promise(),可以获取 jQuery 创建的 Deferred 对象。将这些 Deferreds 包装成ES6 Promises 可以得到比使用计时器更加清晰的代码。

虽然你也可以直接使用 Deferreds,但是通常不建议这样做,因为它们不遵循 Promises/A+ 规范。

最终的代码应该像这样:

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

请注意,在Promise.all()函数中,返回的是一个promise。这是魔法发生的地方。如果在then调用中返回了一个promise,下一个then调用将等待该promise被解决后才执行。
jQuery为每个元素使用动画队列。因此,同一元素上的动画会同步执行。在这种情况下,您根本不需要使用promise!
我已禁用jQuery动画队列,以演示如何使用promise进行操作。 Promise.all()接受一个promise数组,并创建一个新的Promise,在数组中所有promise完成后结束。 Promise.race()也接受一个promise数组,但只有第一个Promise完成时才结束。

1

您的意思是这样的吗:http://jsfiddle.net/LF75a/

您将拥有一个函数调用另一个函数,以此类推,即添加另一个函数调用,然后在其底部添加functionOne

如果我漏掉了什么,请告诉我,希望它符合您的需求:)

或者是这样的:在前一个函数完成后调用函数

代码:

function hulk()
{
  // do some stuff...
}
function simpsons()
{
  // do some stuff...
  hulk();
}
function thor()
{
  // do some stuff...
  simpsons();
}

5
回调函数是我认为在 JavaScript 中最合适的解决方案。 - MalSu
我只有一个函数调用。我不能修改这个函数,但是我需要在第一个函数执行完后执行我的另一个函数。 - Qwerty
我认为这对动画不起作用,因为它们往往需要延迟才能正常工作。 - Muhammad Nour

1

ECMAScript 6 更新

这个使用了 JavaScript 的新特性,叫做 Promises。

functionOne().then(functionTwo);


0

你可以通过回调函数来实现。

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

函数 function1(param, callback) { ...做一些事情 callback(); }


0

您可以使用JavaScript中的Promiseasync/await来实现函数的同步调用。

假设您想以同步方式执行存储在数组中的n个函数,这里是我的解决方案。

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


0

这里有一个n-calls(递归函数)的解决方案。 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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接