JavaScript 中的异步循环

IT技术 javascript
2021-02-09 12:09:53

我需要一个循环,在继续之前等待异步调用。就像是:

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

我怎么能这样做?你有什么想法?

6个回答

如果你阻止脚本,你就不能在 JavaScript 中混合同步和异步。

你需要在这里使用完整的事件驱动方式,幸运的是我们可以隐藏丑陋的东西。

编辑:更新了代码。

function asyncLoop(iterations, func, callback) {
    var index = 0;
    var done = false;
    var loop = {
        next: function() {
            if (done) {
                return;
            }

            if (index < iterations) {
                index++;
                func(loop);

            } else {
                done = true;
                callback();
            }
        },

        iteration: function() {
            return index - 1;
        },

        break: function() {
            done = true;
            callback();
        }
    };
    loop.next();
    return loop;
}

这将为我们提供一个异步的loop,你当然可以进一步修改它,例如一个函数来检查循环条件等。

现在开始测试:

function someFunction(a, b, callback) {
    console.log('Hey doing some stuff!');
    callback();
}

asyncLoop(10, function(loop) {
    someFunction(1, 2, function(result) {

        // log the iteration
        console.log(loop.iteration());

        // Okay, for cycle could continue
        loop.next();
    })},
    function(){console.log('cycle ended')}
);

和输出:

Hey doing some stuff!
0
Hey doing some stuff!
1
Hey doing some stuff!
2
Hey doing some stuff!
3
Hey doing some stuff!
4
Hey doing some stuff!
5
Hey doing some stuff!
6
Hey doing some stuff!
7
Hey doing some stuff!
8
Hey doing some stuff!
9
cycle ended
我对要做什么感到困惑loop.break()如果你愿意,只是一种强制退出的方法吗?
2021-03-27 12:09:53
也许我错过了一些东西,但我不明白这实际上是如何异步的。你不需要 setTimeout 什么的吗?我试过你的代码,取出console.log,把计数调高了很多,但它只是冻结了浏览器。
2021-03-30 12:09:53
就像上面的 Rocketsarefast 所说的那样,这个答案不是异步的,因此完全错误!
2021-04-03 12:09:53
抱歉,由于这实际上不是异步的,因此不得不投反对票。
2021-04-03 12:09:53
像 loop.next 一样,当 done 为 true 时,loop.break 应该被设为无操作: ` break: function() { if (!done) { done = true; 打回来(); } }`
2021-04-10 12:09:53

我简化了这个:

功能:

var asyncLoop = function(o){
    var i=-1;

    var loop = function(){
        i++;
        if(i==o.length){o.callback(); return;}
        o.functionToLoop(loop, i);
    } 
    loop();//init
}

用法:

asyncLoop({
    length : 5,
    functionToLoop : function(loop, i){
        setTimeout(function(){
            document.write('Iteration ' + i + ' <br>');
            loop();
        },1000);
    },
    callback : function(){
        document.write('All done!');
    }    
});

示例: http : //jsfiddle.net/NXTv7/8/

+1 我做了类似的事情,但我把 setTimeout 部分放在了库函数中。
2021-03-18 12:09:53
不是基本上是递归吗?
2021-03-22 12:09:53

@Ivo 建议的一个更简洁的替代方法Asynchronous Method Queue,假设您只需要对集合进行一次异步调用。

(有关更详细的解释,请参阅Dustin Diaz 的这篇文章

function Queue() {
  this._methods = [];
  this._response = null;
  this._flushed = false;
}

(function(Q){

  Q.add = function (fn) {
    if (this._flushed) fn(this._response);
    else this._methods.push(fn);
  }

  Q.flush = function (response) {
    if (this._flushed) return;
    this._response = response;
    while (this._methods[0]) {
      this._methods.shift()(response);
    }
    this._flushed = true;
  }

})(Queue.prototype);

您只需创建 的新实例Queue,添加您需要的回调,然后使用异步响应刷新队列。

var queue = new Queue();

queue.add(function(results){
  for (var result in results) {
    // normal loop operation here
  }
});

someFunction(param1, param2, function(results) {
  queue.flush(results);
}

这种模式的另一个好处是您可以将多个函数添加到队列中,而不仅仅是一个。

如果您有一个包含迭代器函数的对象,您可以在幕后添加对这个队列的支持并编写看起来同步但不是同步的代码:

MyClass.each(function(result){ ... })

只需写入each将匿名函数放入队列而不是立即执行它,然后在异步调用完成时刷新队列。这是一个非常简单而强大的设计模式。

PS 如果您正在使用 jQuery,那么您已经有一个名为jQuery.Deferred的异步方法队列可供您使用

@AdamsetTimeout如果回调只需要一半的时间,我会以哪种方式冒意外行为的风险,那么代码再次执行得更快......那么有什么意义呢?“循环”中的“代码”仍然是有序的,如果你在完整的回调之前做了一些在它之外的事情,你已经在找麻烦了,但是它又是单线程的,我很难想出一个在setTimeout没有进一步错误设计的情况下会破坏某些东西的场景
2021-03-12 12:09:53
@Ivo 我也非常警惕依赖于setTimeout. 如果代码的执行速度比您预期的要快,您就有可能出现意外行为。
2021-03-13 12:09:53
此外,他在另一个问题中要求使用这样的 Node.js module,我在那里说过,为这种“异步同步”循环提供通用解决方案通常是一个坏主意。我宁愿选择与我想要实现的确切要求相匹配的东西。
2021-03-14 12:09:53
好吧,如果正确理解了这个问题,这将不会产生所需的行为,似乎她想做一些回调,在someFunction其中延迟循环的其余部分,您的模式设置了一个函数列表,这些函数将按顺序执行并全部接收结果一个其他函数调用。这是一个很好的模式,但我认为它与所讨论的问题不符。
2021-04-04 12:09:53
@Ivo 没有更多信息,我们不确定,但总的来说,我认为让同步代码在继续之前等待异步操作是糟糕的设计;在我尝试过的每种情况下,由于 JS 是单线程的,它导致了明显的 UI 延迟。如果操作时间过长,您将面临脚本被浏览器强行停止的风险。
2021-04-08 12:09:53

也看看这个出色的库caolan / async您的for循环可以使用mapSeriesseries轻松完成

如果您的示例中有更多详细信息,我可以发布一些示例代码。

我们也可以使用 jquery.Deferred 的帮助。在这种情况下, asyncLoop 函数将如下所示:

asyncLoop = function(array, callback) {
  var nextElement, thisIteration;
  if (array.length > 0) nextElement = array.pop();
  thisIteration = callback(nextElement);
  $.when(thisIteration).done(function(response) {
    // here we can check value of response in order to break or whatever
    if (array.length > 0) asyncLoop(array, collection, callback);
  });
};

回调函数将如下所示:

addEntry = function(newEntry) {
  var deferred, duplicateEntry;
  // on the next line we can perform some check, which may cause async response.
  duplicateEntry = someCheckHere();
  if (duplicateEntry === true) {
    deferred = $.Deferred();
    // here we launch some other function (e.g. $.ajax or popup window) 
    // which based on result must call deferred.resolve([opt args - response])
    // when deferred.resolve is called "asyncLoop" will start new iteration
    // example function:
    exampleFunction(duplicateEntry, deferred);
    return deferred;
  } else {
    return someActionIfNotDuplicate();
  }
};

解决延迟的示例函数:

function exampleFunction(entry, deffered){
  openModal({
    title: "what should we do with duplicate"
    options: [
       {name:"Replace", action: function(){replace(entry);deffered.resolve(replace:true)}},
       {name: "Keep Existing", action: function(){deffered.resolve(replace:false)}}
    ]
  })
}