如何按顺序调用3个函数以便一个接一个地执行它们?

168
如果我需要依次调用这些函数,
$('#art1').animate({'width':'1000px'},1000);        
$('#art2').animate({'width':'1000px'},1000);        
$('#art3').animate({'width':'1000px'},1000);        

我知道在jQuery中可以这样做:

$('#art1').animate({'width':'1000px'},1000,'linear',function(){
    $('#art2').animate({'width':'1000px'},1000,'linear',function(){
        $('#art3').animate({'width':'1000px'},1000);        
    });        
});        

但是,假设我不使用jQuery,我想调用:

some_3secs_function(some_value);        
some_5secs_function(some_value);        
some_8secs_function(some_value);        
我应该如何调用这些函数来执行some_3secs_function,并在此调用结束后,再执行some_5secs_function,在此调用结束后,再调用some_8secs_function更新: 这仍然不起作用:
(function(callback){
    $('#art1').animate({'width':'1000px'},1000);
    callback();
})((function(callback2){
    $('#art2').animate({'width':'1000px'},1000);
    callback2();
})(function(){
    $('#art3').animate({'width':'1000px'},1000);
}));

三个动画同时开始

我的错误在哪里?


你的意思是这些函数需要在恰好3、5和8秒时被调用,还是只需要一个接一个地调用? - Trass Vasston
我认为你只是对同步和异步函数执行不确定。我已经在下面更新了我的答案。希望能帮到你。 - Wayne
试试这个... https://github.com/dineshkani24/queuecall - Dineshkani
11个回答

261

在Javascript中,存在同步和异步函数。

同步函数

Javascript中的大多数函数都是同步函数。如果你连续调用几个同步函数

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();

它们将按顺序执行。doSomethingElse将在doSomething完成之后开始。反过来,doSomethingUsefulThisTime将在doSomethingElse完成之后开始。

异步函数

然而,异步函数不会等待彼此。让我们看一下上面相同的代码示例,这次假设函数是异步的。

doSomething();
doSomethingElse();
doSomethingUsefulThisTime();
函数将按顺序初始化,但它们都会大致同时执行。无法准确预测哪个函数将首先完成:那个需要最短时间执行的函数将首先完成。
但有时,您希望异步执行的函数按顺序执行,有时则希望同步执行的函数异步执行。幸运的是,使用回调和延时这两种方式可以实现这一点。
回调函数
假设我们有三个异步函数要按顺序执行:some_3secs_function, some_5secs_function, 和 some_8secs_function
由于JavaScript中函数可以作为参数传递,因此可以将一个函数作为回调传递,以在函数完成后执行它。
如果我们以以下方式创建这些函数:
function some_3secs_function(value, callback){
  //do stuff
  callback();
}

然后您可以按顺序调用 then,像这样:

some_3secs_function(some_value, function() {
  some_5secs_function(other_value, function() {
    some_8secs_function(third_value, function() {
      //All three functions have completed, in order.
    });
  });
});

超时

Javascript中,你可以设置一个函数在一定时间间隔(以毫秒为单位)后执行。这实际上可以使同步函数表现得像异步函数。

如果我们有三个同步函数,我们可以使用setTimeout函数将它们异步执行。

setTimeout(doSomething, 10);
setTimeout(doSomethingElse, 10);
setTimeout(doSomethingUsefulThisTime, 10);

然而,这有点丑陋,并违反了DRY原则 [wikipedia]。我们可以通过创建一个接受函数数组和超时时间的函数来清理它。

function executeAsynchronously(functions, timeout) {
  for(var i = 0; i < functions.length; i++) {
    setTimeout(functions[i], timeout);
  }
}

这可以这样调用:

executeAsynchronously(
    [doSomething, doSomethingElse, doSomethingUsefulThisTime], 10);

简而言之,如果你有想要同步执行的异步函数,请使用回调函数;如果你有想要异步执行的同步函数,请使用定时器。


7
这不会延迟3、5和8秒的功能,就像示例所建议的那样,它们只是一个接一个地运行。 - Trass Vasston
1
@lwburk 很好的问题。现在想起来,我一点都不知道。 - Peter Olson
10
@Peter - 对于我所见过的最美丽、最复杂的按顺序调用三个同步函数的方法,我给你点赞。 - Wayne
4
感谢您精准地解释了异步和同步JavaScript函数之间的区别。这让我受益匪浅。 - jnelson
2
以下是不正确的原因:(1) 3个超时都将在10秒后解决,因此所有3行同时触发。(2) 此方法要求您提前知道持续时间并“安排”函数在未来发生,而不是等待链中较早的异步函数解决并成为触发器。--- 相反,您需要使用以下答案之一,使用回调、承诺或异步库。 - zeroasterisk
显示剩余11条评论

40

这个答案使用了 ECMAScript 6 标准中的 JavaScript 特性 promises。如果您的目标平台不支持 promises,请使用 PromiseJs 进行填充。

如果你想使用 jQuery 的动画效果,请查看我在这里的回答:Wait till a Function with animations is finished until running another Function

这是一个使用 ES6 PromisesjQuery 动画效果的代码示例。

Promise.resolve($('#art1').animate({ 'width': '1000px' }, 1000).promise()).then(function(){
    return Promise.resolve($('#art2').animate({ 'width': '1000px' }, 1000).promise());
}).then(function(){
    return Promise.resolve($('#art3').animate({ 'width': '1000px' }, 1000).promise());
});

正常的方法也可以用Promises包装。

new Promise(function(fulfill, reject){
    //do something for 5 seconds
    fulfill(result);
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 5 seconds
        fulfill(result);
    });
}).then(function(result){
    return new Promise(function(fulfill, reject){
        //do something for 8 seconds
        fulfill(result);
    });
}).then(function(result){
    //do something with the result
});

then方法在Promise完成后立即执行。通常,传递给thenfunction返回值作为结果传递到下一个function中。

但是,如果返回了一个Promise,则下一个then函数将等待Promise执行完毕,并接收其结果(即传递给fulfill的值)。


我知道这很有用,但是发现没有一个真实世界的例子来给它一些上下文,所以很难理解所给出的代码。我在YouTube上找到了这个视频:https://www.youtube.com/watch?v=y5mltEaQxa0 - 并在这里写出了视频的源代码 https://drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/view?usp=sharing 这个例子中还有一些细微之处,比如缺少catch,这个视频对此进行了详细说明。(在getPostById()行中使用不同的id或尝试更改作者的名称,使其不匹配帖子等) - JGFMK

21

看起来你没有完全理解同步和异步函数执行之间的区别。

在你更新提供的代码中,每个回调函数都会立即执行,然后立即启动一个动画。但是,这些动画却是异步执行的。它的工作方式如下:

  1. 执行动画中的一步
  2. 调用包含下一个动画步骤和延迟的函数的setTimeout
  3. 等待一段时间
  4. setTimeout给定的回调函数执行
  5. 返回到步骤1

这将持续到动画的最后一步完成。与此同时,你的同步函数早已完成。换句话说,你对animate函数的调用并不真正需要3秒钟。它通过延迟和回调函数模拟效果。

你需要的是队列。在内部,jQuery将动画排队,只有在相应的动画完成后才执行你的回调函数。如果你的回调函数随后启动了另一个动画,效果就是它们按顺序执行。

在最简单的情况下,这相当于以下内容:

window.setTimeout(function() {
    alert("!");
    // set another timeout once the first completes
    window.setTimeout(function() {
        alert("!!");
    }, 1000);
}, 3000); // longer, but first

这是一个通用的异步循环函数。它会按顺序调用给定的函数,在每个函数之间等待指定秒数。

function loop() {
    var args = arguments;
    if (args.length <= 0)
        return;
    (function chain(i) {
        if (i >= args.length || typeof args[i] !== 'function')
            return;
        window.setTimeout(function() {
            args[i]();
            chain(i + 1);
        }, 2000);
    })(0);
}    

用法:

loop(
  function() { alert("sam"); }, 
  function() { alert("sue"); });

你显然可以修改它,以使用可配置的等待时间,或立即执行第一个函数,或在链中的函数返回false时停止执行,或在指定上下文中apply函数,或者任何其他你可能需要的操作。


15

我相信 async 库能够为您提供一种非常优雅的方式来完成这个任务。尽管使用 Promise 和回调函数可能有些难以协调,但是 async 可以提供整齐的模式来简化您的思考过程。要按顺序运行函数,您需要将它们放入一个 async waterfall 中。在 async 的术语中,每个函数都被称为一个 task,它接受一些参数和一个 callback,该回调函数是下一个要执行的函数。基本结构应该如下所示:

async.waterfall([
  // A list of functions
  function(callback){
      // Function no. 1 in sequence
      callback(null, arg);
  },
  function(arg, callback){
      // Function no. 2 in sequence
      callback(null);
  }
],    
function(err, results){
   // Optional final callback will get results for all prior functions
});

我刚刚尝试简要解释了这里的结构。阅读指南可以获取更多信息,它写得非常好。


1
这确实使得JS更加容易处理。 - mike

9

你的函数应该接收一个回调函数,在完成时调用它。

function fone(callback){
...do something...
callback.apply(this,[]);

}

function ftwo(callback){
...do something...
callback.apply(this,[]);
}

然后使用方法如下:

fone(function(){
  ftwo(function(){
   ..ftwo done...
  })
});

4
asec=1000; 

setTimeout('some_3secs_function("somevalue")',asec*3);
setTimeout('some_5secs_function("somevalue")',asec*5);
setTimeout('some_8secs_function("somevalue")',asec*8);

我不会在这里深入讨论setTimeout,但是:

  • 在这种情况下,我已将代码添加为字符串形式执行。这是将变量传递到您的setTimeout函数中的最简单方法,但是纯粹主义者会抱怨。
  • 您也可以传递一个没有引号的函数名称,但不能传递变量。
  • 您的代码不会等待setTimeout触发。
  • 这一点起初可能很难理解:由于之前提到的原因,如果从调用函数传递变量,则该变量将不再存在于超时触发时 - 调用函数将已执行并且其变量已经消失。
  • 我已知使用匿名函数来解决所有这些问题,但是可能还有更好的方法,

4
你也可以这样使用 promises:

你也可以这样使用 promises:

    some_3secs_function(this.some_value).then(function(){
       some_5secs_function(this.some_other_value).then(function(){
          some_8secs_function(this.some_other_other_value);
       });
    });

为了从 .then 内部访问 some_value,您需要将其设置为全局变量。

或者您可以从外部函数中返回内部函数使用的值,像这样:

    one(some_value).then(function(return_of_one){
       two(return_of_one).then(function(return_of_two){
          three(return_of_two);
       });
    });

ES6更新

现在已经广泛支持async/await,这是实现相同功能的方法:

async function run(){    
    await $('#art1').animate({'width':'1000px'},1000,'linear').promise()
    await $('#art2').animate({'width':'1000px'},1000,'linear').promise()
    await $('#art3').animate({'width':'1000px'},1000,'linear').promise()
}

这基本上是将您的函数“Promisifying”(如果它们尚未是异步的),然后等待它们。


4
由于您标记了JavaScript,我建议使用计时器控件,因为您的函数名称分别为3、5和8秒。因此,开始计时器,在3秒后调用第一个函数,在5秒后调用第二个函数,在8秒后调用第三个函数,然后在完成后停止计时器。
通常在JavaScript中,您所拥有的对于函数逐一运行是正确的,但由于看起来您正在尝试进行定时动画,计时器将是最佳选择。

3

//sample01
(function(_){_[0]()})([
 function(){$('#art1').animate({'width':'10px'},100,this[1].bind(this))},
 function(){$('#art2').animate({'width':'10px'},100,this[2].bind(this))},
 function(){$('#art3').animate({'width':'10px'},100)},
])

//sample02
(function(_){_.next=function(){_[++_.i].apply(_,arguments)},_[_.i=0]()})([
 function(){$('#art1').animate({'width':'10px'},100,this.next)},
 function(){$('#art2').animate({'width':'10px'},100,this.next)},
 function(){$('#art3').animate({'width':'10px'},100)},
]);

//sample03
(function(_){_.next=function(){return _[++_.i].bind(_)},_[_.i=0]()})([
 function(){$('#art1').animate({'width':'10px'},100,this.next())},
 function(){$('#art2').animate({'width':'10px'},100,this.next())},
 function(){$('#art3').animate({'width':'10px'},100)},
]);


请问您能解释一下这是什么吗?下划线绑定了什么?分配给next的函数是做什么用的? - mtso
1
我将使用jsfiddle来解释示例2。 https://jsfiddle.net/mzsteyuy/3/ 如果您允许我粗略地解释,示例2是jsfiddle中代码的简短方式。 下划线是一个数组,其元素是计数器变量(i)和函数next以及函数[0]〜[2]。 - yuuya

1
我使用基于JavaScript的setTimeout函数来实现'waitUntil'功能。
/*
    funcCond : function to call to check whether a condition is true
    readyAction : function to call when the condition was true
    checkInterval : interval to poll <optional>
    timeout : timeout until the setTimeout should stop polling (not 100% accurate. It was accurate enough for my code, but if you need exact milliseconds, please refrain from using Date <optional>
    timeoutfunc : function to call on timeout <optional>
*/
function waitUntil(funcCond, readyAction, checkInterval, timeout, timeoutfunc) {
    if (checkInterval == null) {
        checkInterval = 100; // checkinterval of 100ms by default
    }
    var start = +new Date(); // use the + to convert it to a number immediatly
    if (timeout == null) {
        timeout = Number.POSITIVE_INFINITY; // no timeout by default
    }
    var checkFunc = function() {
        var end = +new Date(); // rough timeout estimations by default

        if (end-start > timeout) {
            if (timeoutfunc){ // if timeout function was defined
                timeoutfunc(); // call timeout function
            }
        } else {
            if(funcCond()) { // if condition was met
                readyAction(); // perform ready action function
            } else {
                setTimeout(checkFunc, checkInterval); // else re-iterate
            }
        }
    };
    checkFunc(); // start check function initially
};

如果您的函数设置了特定条件并将其设置为true,那么它将完美地工作,您可以对其进行轮询。此外,它还提供了超时功能,以便在函数未能执行某些操作(甚至在时间范围内)时,您可以选择其他替代方案。考虑用户反馈!
doSomething();
waitUntil(function() { return doSomething_value===1;}, doSomethingElse);
waitUntil(function() { return doSomethingElse_value===1;}, doSomethingUseful);

注意事项

Date 会导致大致的超时估计。为了更高的精度,请使用 console.time() 等函数。但是需要注意的是,Date 提供了更好的跨浏览器和遗留支持。如果您不需要精确的毫秒级别的测量,请勿费力,或者在浏览器支持时提供 console.time()。


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