【问题标题】:How should I call 3 functions in order to execute them one after the other?我应该如何调用 3 个函数才能一个接一个地执行它们?
【发布时间】:2011-07-08 11:04:05
【问题描述】:

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

$('#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 秒内准确调用函数还是一个接一个地调用函数?
  • 认为您只是不确定同步与异步函数执行。我在下面更新了我的答案。希望对您有所帮助。

标签: javascript asynchronous callback closures


【解决方案1】:

在Javascript中,有同步异步函数。

同步函数

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

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

它们将按顺序执行。 doSomethingElse 将在 doSomething 完成之前启动。反过来,doSomethingUsefulThisTimedoSomethingElse 完成之前不会启动。

异步函数

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

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

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

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

回调

假设我们要按顺序执行三个异步函数,some_3secs_functionsome_5secs_functionsome_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 principle[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 秒,如示例所示,它们只会一个接一个地运行。
  • @Peter - 等等,我很困惑。如果这些是碰巧需要几秒钟才能完成的简单同步调用,那么我们为什么需要这些呢?
  • @Peter - +1 是我见过的最漂亮、最复杂的方法,用于依次调用三个同步函数。
  • 感谢您专业地解释异步和同步 js 函数之间的区别。这说明了很多。
  • 这是 NOT 正确的,原因如下:(1) 3 次超时将在 10 秒后全部解决,因此所有 3 行同时触发。 (2) 此方法要求您提前知道持续时间并“安排”函数在未来发生,而不是等待链中较早的异步函数来解决并作为触发器。 --- 相反,您想通过回调、承诺或异步库来使用以下答案之一。
【解决方案2】:

此答案使用promises,这是ECMAScript 6 标准的JavaScript 功能。如果您的目标平台不支持promises,请使用PromiseJs 填充它。

如果您想使用 jQuery 动画,请在此处查看我的回答 Wait till a Function with animations is finished until running another Function

下面是使用ES6 PromisesjQuery 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 完成后立即执行。通常,传递给thenfunction 的返回值作为结果传递给下一个。

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

【讨论】:

  • 我知道这很有用,但发现给出的代码很难理解,除非寻找一个真实世界的例子来给它一些上下文。我在 YouTube 上找到了这个视频:youtube.com/watch?v=y5mltEaQxa0 - 并在此处写下了视频的来源drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/… 还有一些细微差别,例如此示例中缺少的捕获物,对此进行了详细说明。 (在 getPostById() 行中使用不同的 id 或尝试更改作者的姓名,使其与帖子不匹配等)
【解决方案3】:

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

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

  1. 在动画中执行一个步骤
  2. 使用包含下一个动画步骤和延迟的函数调用 setTimeout
  3. 一段时间过去了
  4. setTimeout的回调执行
  5. 返回步骤 1

这会一直持续到动画的最后一步完成。同时,您的同步功能早已完成。换句话说,您对animate 函数的调用并不真的 需要3 秒。使用延迟和回调模拟效果。

您需要的是一个队列。在内部,jQuery 对动画进行排队,只有在其相应的动画完成后才执行 your 回调。如果您的回调然后启动另一个动画,效果是它们按顺序执行。

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

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 指定上下文中的函数或其他任何你可能需要。

【讨论】:

    【解决方案4】:

    我相信async 库将为您提供一种非常优雅的方式来执行此操作。虽然 Promise 和回调可能有点难以处理,但 async 可以提供简洁的模式来简化您的思维过程。要串行运行函数,您需要将它们放在异步waterfall 中。在异步术语中,每个函数都称为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
    });
    

    我只是试图简要解释一下这里的结构。阅读瀑布guide 了解更多信息,它写得很好。

    【讨论】:

    • 这确实让 JS 更能忍受一点。
    【解决方案5】:

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

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

    那么用法如下:

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

    【讨论】:

      【解决方案6】:
      asec=1000; 
      
      setTimeout('some_3secs_function("somevalue")',asec*3);
      setTimeout('some_5secs_function("somevalue")',asec*5);
      setTimeout('some_8secs_function("somevalue")',asec*8);
      

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

      • 在这种情况下,我添加了要作为字符串执行的代码。这是将 var 传递给 setTimeout 函数的最简单方法,但纯粹主义者会抱怨。
      • 您也可以传递不带引号的函数名,但不能传递变量。
      • 您的代码不会等待 setTimeout 触发。
      • 一开始可能很难理解这一点:由于前一点,如果您从调用函数中传递一个变量,则在触发超时时该变量将不再存在 - 调用函数将具有执行,它的 vars 消失了。
      • 众所周知,我使用匿名函数来解决这一切,但很可能有更好的方法,

      【讨论】:

        【解决方案7】:

        由于您使用 javascript 对其进行了标记,因此我会使用计时器控件,因为您的函数名称是 3、5 和 8 秒。所以启动你的计时器,3 秒后,调用第一个,5 秒调用第二个,8 秒调用第三个,然后当它完成时,停止计时器。

        通常在 Javascript 中,您所拥有的功能是正确的,因为函数会一个接一个地运行,但由于您似乎正在尝试制作定时动画,因此最好使用计时器。

        【讨论】:

          【解决方案8】:

          //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 的函数有什么作用?
          • 我使用 jsfiddle 解释示例 2。 jsfiddle.net/mzsteyuy/3 如果您允许我大致解释一下,示例 2 是 jsfiddle 中代码的一种简短方式。下划线是 Array 哪些元素是 counter valiable( i ) and function next and function [0] ~[2].
          【解决方案9】:

          你也可以通过这种方式使用 Promise:

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

          您必须将 some_value 设为全局才能从 .then 内部访问它

          或者,您可以从外部函数返回内部函数将使用的值,如下所示:

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

          这基本上是在“承诺”您的函数(如果它们还不是异步的),然后等待它们

          【讨论】:

            【解决方案10】:

            我使用基于 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);
            

            备注

            日期会导致粗略的超时估计。要获得更高的精度,请切换到 console.time() 等函数。请注意,Date 提供了更好的跨浏览器和旧版支持。如果您不需要精确的毫秒测量;不要打扰,或者,包装它,并在浏览器支持时提供 console.time()

            【讨论】:

              【解决方案11】:

              如果方法 1 必须在方法 2、3、4 之后执行。下面的代码 sn-p 可以是在 JavaScript 中使用 Deferred 对象的解决方案。

              function method1(){
                var dfd = new $.Deferred();
                   setTimeout(function(){
                   console.log("Inside Method - 1"); 
                   method2(dfd);	 
                  }, 5000);
                return dfd.promise();
              }
              
              function method2(dfd){
                setTimeout(function(){
                 console.log("Inside Method - 2"); 
                 method3(dfd);	
                }, 3000);
              }
              
              function method3(dfd){
                setTimeout(function(){
                 console.log("Inside Method - 3"); 	
                 dfd.resolve();
                }, 3000);
              }
              
              function method4(){   
                 console.log("Inside Method - 4"); 	
              }
              
              var call = method1();
              
              $.when(call).then(function(cb){
                method4();
              });
              &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"&gt;&lt;/script&gt;

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-10-22
                • 2013-10-02
                • 2014-02-07
                • 2021-08-22
                • 2021-11-01
                • 2013-12-22
                • 1970-01-01
                相关资源
                最近更新 更多