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


150

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

$('#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秒内或仅一个接一个地调用这些函数?
2011年

认为您只是不确定同步与异步函数的执行。我在下面更新了我的答案。希望能帮助到你。
韦恩

Answers:


242

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

然后您可以按如下顺序依次致电:

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秒,它们只会一个接一个地运行。
特拉斯·瓦斯顿

1
@Peter-等等,我很困惑。如果这些简单的同步调用恰好需要几秒钟才能完成,那么为什么我们需要其中的任何一个呢?
韦恩

9
@Peter-+1是我见过的最漂亮,最复杂的方法,用于依次调用三个同步函数。
韦恩

4
感谢您熟练地解释异步和同步js函数之间的区别。这解释了很多。
jnelson 2014年

2
由于以下原因,这是正确的:(1)3个超时将在10秒后全部解决,因此所有3条线路同时触发。(2)此方法要求您提前知道持续时间,并在将来发生“计划”功能,而不是等待链中较早的异步功能解决并使其触发。---相反,您想通过回调,promise或异步库使用以下答案之一。
zeroasterisk

37

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

在这里查看我的答案。如果要使用动画,请等到带有动画的功能完成后再运行另一个功能jQuery

这是您使用ES6 Promisesand 编写的代码的样子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作为结果传递给下一个。

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


我知道这很有用,但是发现在不寻找真实示例来提供一些上下文的情况下,很难理解给出的代码。我在YouTube上找到了此视频:youtube.com/watch?v=y5mltEaQxa0-并在此处从视频中写下了源代码drive.google.com/file/d/1NrsAYs1oaxXw0kv9hz7a6LjtOEb6x7z-/…还有一些细微差别,例如渔获这个例子,详细说明。(在getPostById()行中使用其他ID,或尝试更改作者的姓名,使其与帖子不匹配,等等)
JGFMK

20

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

您在更新中提供的代码将立即执行您的每个回调函数,这些回调函数将立即启动动画。但是,动画是异步执行的。它是这样的:

  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在指定上下文中返回该功能时停止执行,或者可能需要执行其他任何操作。


14

我相信异步库将为您提供一种非常优雅的方法。虽然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
});

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


1
这确实使JS更易于使用。
迈克·

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,但是:

  • 在这种情况下,我添加了代码以字符串形式执行。这是将var传递到setTimeout-ed函数中的最简单方法,但是纯粹主义者会抱怨。
  • 您还可以传递不带引号的函数名称,但不能传递变量。
  • 您的代码不等待setTimeout触发。
  • 首先可能很难理解这一点:由于上一点,如果您从调用函数传递了一个变量,那么在超时触发时该变量将不再存在-调用函数将已经执行,并且vars走了。
  • 众所周知,我可以使用匿名函数来解决所有这些问题,但是可能会有更好的方法,

3

由于您使用javascript对其进行了标记,因此我将使用计时器控件,因为您的函数名称分别为3、5和8秒。因此,启动计时器(3秒钟,调用第一个,5秒钟,调用第二个,8秒钟,调用第三个),然后在完成时停止计时器。

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


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


你能解释一下这是什么吗?下划线是什么?分配的功能有什么作用next
mtso

1
我用jsfiddle解释了示例2。 jsfiddle.net/mzsteyuy/3 如果您允许我粗略地解释一下,示例2是jsfiddle中代码的一种简短表示。下划线是Array,其元素是计数器变量(i),其次是功能,而功能为[0]〜[2]。
yuuya

1

您还可以通过以下方式使用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);
       });
    });

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


0

如果必须在方法2、3、4之后执行方法1,则以下代码段可以是使用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>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.