带延迟串行执行异步函数

Executing asynchronous functions serially with a delay

本文关键字:异步 函数 执行 延迟      更新时间:2024-03-27

最初我有这样的东西(ads是一个数组):

for (var i = 0; i < ads.length; i++) {
    asynchMeth(ad[i]);
}

asyncMeth是一个调用服务器的异步方法(我不能使它同步)。但我希望asynchMeth(ad[I])将在asynchMeth(ad[I-1])完成后3秒启动。以下不起作用,但它给出了我想要的东西的想法:

    isWorking = false; //Will be set to true whenever asyncMeth starts, to false when it ends.
     var i = 0;
     var timer = setInterval(3000, function() {
       if(!isWorking){
          if(i < ads.length){
               asyncMeth(ads[i]);
               i++;
           }
           else{
                clearInterval(timer);
           }
        }
    });

当我们调用具有不同/动态参数的函数时,如何使用setInterval?

我首先会向asyncMeth添加一个回调,每当函数完成它正在做的任何事情时都会调用它。否则就无法知道asyncMeth是否已经完成了它的工作。在你的例子中,你拥有的和你想要的不一样!您希望ads[i]ads[i - 1]完成后处理3秒。相反,您所拥有的是在ads[i - 1]开始处理后3秒开始处理ads[i]的代码。通过回调asyncMeth,您可以执行以下操作:

(function work(i) {
    if(i < ads.length) {
        asyncMeth(ads[i], function() {
            work(++i);
        });
    }
}(0);

这里有一个自调用函数,它最初接受参数值0。该值被指定给参数i。如果i小于ads.length,则意味着我们仍有项目要处理。因此它以ads[i]为参数调用asyncMeth

但我们也提供了回调。在这个回调中,我们告诉回调调用work,并增加i的值。这意味着work将开始处理下一个项目。

因此,现在您可以一个接一个地异步处理每个项目,直到i等于ads.length,此时您已经处理了所有项目。

编辑

我注意到你提到你想要3秒的延迟。为此,你可以这样做:

(function work(i) {
    if(i < ads.length) {
        asyncMeth(ads[i], function() {
            setTimeout(function() {
                work(++i);
            }, 3000);
        });
    }
}(0);

这里唯一的区别是,下一次迭代发生在上一次迭代完成后3秒。

AFAICT,这里有两个答案:你想要的答案和你需要的答案。如果其他人偶然发现谷歌的这个答案,我会同时给出这两个答案。

您应该做什么

看起来你正试图让一堆异步调用按顺序发生,并猜测它们会在大约三秒后返回。当然,如果这不是一个准确的猜测,你就会遇到问题,所以你真正想要的是一个在请求返回时立即触发的函数。

有两种方法可以做到这一点——要么是延续传递风格(又名回调地狱),要么是使用promise。前者在概念上更容易,但在匆忙中变得丑陋:

$http('url', function (data) {
  //this function is called when the AJAX request comes back
  //do something with data
  $http('url2', function (data) {
     //do something with second piece of data after request 2 loads
  })
})

当你需要一个任意的数字时,这会变得非常混乱。我的首选解决方案是递归的高阶函数,如以下所示,尽管可以迭代执行:

var launch = function f (i) {
  return function (data) {
    //do something with data
    if (i <= urls.length - 1)
      $http(urls[i+1], f(i+1))
  }
}
//manually trigger the first one
launch(0)()

更干净的方式是承诺。关于如何使用promise来清理异步代码,有数百个教程,下面是一个如何对不同数量的请求进行清理的示例。

你问了什么

如果你真的想用不同的参数启动一堆超时,那么诀窍是关闭每个超时来缓存i的值。如果你这样做:

for (var i = 0; i < 20; i++) {
  setTimeout(function () {
    //i is 20 by the time this runs...
  }, 1000)
}

那么该函数的所有20次调用都会将i看作20,这可能不是您想要的。

解决方案是:

for (var i = 0; i < 20; i++) {
  setTimeout((function (j) {
    //do something with j, which is a copy of the value of i for this iteration
  })(i), 1000)
}

这种方法之所以有效,是因为显式传递的基元函数参数(即数字)是按值传递的,而通过闭包"传递"变量(这就是断开的例子中发生的情况)是通过引用传递的,即使是基元也是如此。