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

时间:2011-03-03 23:32:48

标签: javascript asynchronous callback closures

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

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

三个动画同时开始

我的错误在哪里。

11 个答案:

答案 0 :(得分:216)

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

同步功能

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

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

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

异步函数

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

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

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

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

回调

假设我们要按顺序执行三个异步函数,some_3secs_functionsome_5secs_functionsome_8secs_function

由于函数可以作为Javascript中的参数传递,因此可以在函数完成后将函数作为回调函数传递。

如果我们创建像这样的函数

function some_3secs_function(value, callback){
  //do stuff
  callback();
}

然后你可以按顺序打电话,就像这样:

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

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

答案 1 :(得分:29)

此答案使用promisesECMAScript 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完成后立即执行。通常,传递给function的{​​{1}}的返回值将作为结果传递给下一个。

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

答案 2 :(得分:20)

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

您在更新中立即提供的代码会执行每个回调函数,这些函数会立即启动动画。但是,动画会执行 asyncronously 。它的工作原理如下:

  1. 在动画中执行一个步骤
  2. 使用包含下一个动画步骤和延迟
  3. 的函数调用setTimeout
  4. 一段时间过去了
  5. 授予setTimeout的回调执行
  6. 返回第1步
  7. 这一直持续到动画的最后一步完成。与此同时,您的同步功能早已完成。换句话说,您对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指定上下文中的函数时停止执行不管你需要什么。

答案 3 :(得分:9)

我相信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以获取更多信息,它写得非常好。

答案 4 :(得分:8)

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

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

}

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

那么用法就像:

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

答案 5 :(得分: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,但是:

  • 在这种情况下,我添加了以字符串形式执行的代码。这是将var传递到setTimeout-ed函数的最简单方法,但纯粹主义者会抱怨。
  • 您也可以传递没有引号的函数名称,但不能传递任何变量。
  • 您的代码不会等待setTimeout触发。
  • 这个可能很难让你头脑发热:由于前一点,如果从调用函数传递一个变量,那么在超时触发时该变量将不再存在 - 调用函数将具有已经执行了并且它已经消失了。
  • 我知道使用匿名函数解决所有这些问题,但可能有更好的方法,

答案 6 :(得分:3)

由于你用javascript标记了它,我会使用定时器控件,因为你的函数名是3秒,5秒和8秒。所以启动你的计时器,3秒钟,打电话给第一个,5秒打电话给第二个,8秒打电话给第三个,然后当它完成时,停止计时器。

通常在Javascript中你所拥有的是正确的,因为这些函数是一个接一个地运行,但是因为看起来你正在尝试做定时动画,所以定时器将是你最好的选择。

答案 7 :(得分:2)

//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)},
]);

答案 8 :(得分:1)

你也可以这样使用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);
       });
    });

您必须使some_value全局,才能从.then

中访问它

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

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

答案 9 :(得分:0)

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

答案 10 :(得分:0)

如果方法1必须在方法2、3、4之后执行。下面的代码段可以是使用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();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>