我应该如何调用 3 个函数以便一个接一个地执行它们?

IT技术 javascript asynchronous callback closures
2021-01-12 09:19:57

如果我需要一个接一个地调用这个函数,

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

三个动画同时开始

我的错误在哪里?

6个回答

在 Javascript 中,有同步异步功能。

同步功能

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

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

他们将按顺序执行。在完成doSomethingElse之前不会开始doSomethingdoSomethingUsefulThisTime,反过来,doSomethingElse在完成之前不会启动

异步函数

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

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

函数将按顺序初始化,但它们将大致同时执行。您无法始终如一地预测哪个将首先完成:恰好花费最短执行时间的那个将首先完成。

但有时,您希望异步函数按顺序执行,有时您希望同步函数异步执行。幸运的是,这可以分别通过回调和超时实现。

回调

假设我们有三个要按顺序执行的异步函数some_3secs_functionsome_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);

总之,如果您有要同步执行的异步函数,请使用回调,如果您有要异步执行的同步函数,请使用超时。

这不会将函数延迟 3,5 和 8 秒,正如示例所建议的那样,它们只会一个接一个地运行。
2021-03-17 09:19:57
@Peter - 等等,所以我很困惑。如果这些是恰好需要几秒钟才能完成的简单同步调用,那么我们为什么需要这些呢?
2021-03-27 09:19:57
由于以下原因,这是正确的:(1) 3 次超时将在 10 秒后全部解决,因此所有 3 行同时触发。(2) 这种方法要求你提前知道持续时间和“安排”函数在未来发生,而不是等待链中较早的异步函数解决,这就是触发器。--- 相反,您希望通过回调、Promise或异步库使用以下答案之一。
2021-03-28 09:19:57
感谢您专业地解释异步和同步 js 函数之间的区别。这说明了很多。
2021-03-30 09:19:57
@Peter - 为我见过的按顺序调用三个同步函数的最漂亮、最复杂的方法+1。
2021-03-31 09:19:57

这个答案使用标准promises的 JavaScript 特性ECMAScript 6如果您的目标平台不支持promises,请使用PromiseJs对其进行polyfill

如果您想使用动画,请在此处查看我的答案,等待带有动画的函数完成,直到运行另一个函数jQuery

这是您的代码与ES6 Promises和 的样子jQuery animations

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完成后立即执行通常,function传递给的返回值then作为结果传递给下一个。

但是如果 aPromise被返回,下一个then函数会一直等到Promise执行完成并接收它的结果(传递给 的值fulfill)。

我知道这很有用,但发现给出的代码很难理解,而无需寻找真实世界的示例来为其提供一些上下文。我在YouTube上发现这个视频:youtube.com/watch?v=y5mltEaQxa0 -从这里视频写了源drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/...有一些细微之处更喜欢捕捉缺失这个例子详细说明。(在 getPostById() 行中使用不同的 id 或尝试更改作者的姓名,使其与帖子等不匹配)
2021-04-03 09:19:57

听起来您没有完全理解同步异步函数执行之间的区别

您在更新中提供的代码会立即执行您的每个回调函数,然后立即启动动画。然而,动画是异步执行的它是这样工作的:

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

您显然可以修改它以获取可配置的等待时间或立即执行第一个函数或在链falseapply的函数返回时停止执行在指定上下文中的函数或您可能需要的任何其他内容。

我相信异步库将为您提供一种非常优雅的方式来做到这一点。虽然Promise和回调可能有点难以处理,但异步可以提供简洁的模式来简化您的思考过程。要串行运行函数,您需要将它们放在异步瀑布中在异步术语中,每个函数都被称为 a task,它带有一些参数和 a 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
});

我只是试图在这里简要解释一下结构。通读瀑布指南以获取更多信息,它写得很好。

这确实让 JS 更容易忍受。
2021-04-06 09:19:57

你的函数应该接受一个回调函数,当它完成时被调用。

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

}

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

那么用法如下:

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