【问题标题】:Asynchronous for cycle in JavaScriptJavaScript中的异步循环
【发布时间】:2011-05-16 09:11:19
【问题描述】:

我需要一个循环来等待异步调用,然后再继续。比如:

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

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

【问题讨论】:

  • ( /* ... */ ) 看起来像个怪物,我现在很害怕 :(

标签: javascript


【解决方案1】:

我需要调用一些异步函数X 次,每次迭代都必须在上一次完成之后发生,所以我写了一个litte library 可以这样使用:

// https://codepen.io/anon/pen/MOvxaX?editors=0012
var loop = AsyncLoop(function(iteration, value){
  console.log("Loop called with iteration and value set to: ", iteration, value);

  var random = Math.random()*500;

  if(random < 200)
    return false;

  return new Promise(function(resolve){
    setTimeout(resolve.bind(null, random), random);
  });
})
.finished(function(){
  console.log("Loop has ended");
});

每次调用用户定义的循环函数,它都有两个参数,迭代索引和前一次调用返回值。

这是一个输出示例:

"Loop called with iteration and value set to: " 0 null
"Loop called with iteration and value set to: " 1 496.4137048207333
"Loop called with iteration and value set to: " 2 259.6020382449663
"Loop called with iteration and value set to: " 3 485.5400568702862
"Loop has ended"

【讨论】:

    【解决方案2】:

    可以使用 ES7 中引入的async await

    for ( /* ... */ ) {
        let result = await someFunction(param1, param2);
    }
    alert("For cycle ended");
    

    这仅在 someFunction 返回 Promise 时有效!

    如果someFunction 没有返回一个 Promise,那么你可以让它自己返回一个 Promise,如下所示:

    function asyncSomeFunction(param1,praram2) {
      return new Promise((resolve, reject) => {
        someFunction(praram1,praram2,(result)=>{
          resolve(result);
        })
      })
    }
    

    然后将await someFunction(param1, param2);这一行替换为await asynSomeFunction(param1, param2);

    async await代码前请先理解Promise!

    【讨论】:

    • 这应该是Unexpected await inside loop
    • @Reyraa 不是javascript 问题。该警告来自您的 eslint 配置。我总是从eslint 禁用该规则,因为在大多数地方我真正需要在循环内等待
    【解决方案3】:

    这是另一个我认为比其他示例更具可读性的示例,您将异步函数包装在一个函数中,该函数接受 done 函数、当前循环索引以及上一个异步调用的结果(如果有) :

    function (done, i, prevResult) {
       // perform async stuff
       // call "done(result)" in async callback 
       // or after promise resolves
    }
    

    一旦done() 被调用,它就会触发下一个异步调用,再次传入完成函数、当前索引和上一个结果。整个循环完成后,将调用提供的循环callback

    这是一个可以运行的 sn-p:

    asyncLoop({
      limit: 25,
      asyncLoopFunction: function(done, i, prevResult) {
        setTimeout(function() {
          console.log("Starting Iteration: ", i);
          console.log("Previous Result: ", prevResult);
          var result = i * 100;
          done(result);
        }, 1000);
      },
      initialArgs: 'Hello',
      callback: function(result) {
        console.log('All Done. Final result: ', result);
      }
    });
    
    function asyncLoop(obj) {
      var limit = obj.limit,
        asyncLoopFunction = obj.asyncLoopFunction,
        initialArgs = obj.initialArgs || {},
        callback = obj.callback,
        i = 0;
    
      function done(result) {
        i++;
        if (i < limit) {
          triggerAsync(result);
        } else {
          callback(result);
        }
      }
    
      function triggerAsync(prevResult) {
        asyncLoopFunction(done, i, prevResult);
      }
    
      triggerAsync(initialArgs); // init
    }

    【讨论】:

      【解决方案4】:

      基于 Promise 库的解决方案:

      /*
          Since this is an open question for JS I have used Kris Kowal's Q promises for the same
      */
      
      var Q = require('q');
      /*
          Your LOOP body
          @success is a parameter(s) you might pass
      */
      var loopBody = function(success) {
          var d = Q.defer(); /* OR use your favorite promise library like $q in angular */
          /*
              'setTimeout' will ideally be your node-like callback with this signature ... (err, data) {}
              as shown, on success you should resolve 
              on failure you should reject (as always ...) 
          */
          setTimeout(function(err, data) {
              if (!err) {
                  d.resolve('success');
              } else {
                  d.reject('failure');
              }
          }, 100); //100 ms used for illustration only 
          return d.promise;
      };
      
      /*
          function to call your loop body 
      */
      function loop(itr, fn) {
          var def = Q.defer();
          if (itr <= 0) {
              def.reject({ status: "un-successful " });
          } else {
              var next = loop.bind(undefined, itr - 1, fn); // 'next' is all there is to this 
              var callback = fn.bind(undefined /*, a, b, c.... */ ); // in case you want to pass some parameters into your loop body
              def.promise = callback().then(def.resolve, next);
          }
          return def.promise;
      }
      /*
          USAGE: loop(iterations, function(){})
          the second argument has to be thenable (in other words return a promise)
          NOTE: this loop will stop when loop body resolves to a success
          Example: Try to upload file 3 times. HURRAY (if successful) or log failed 
      */
      
      loop(4, loopBody).then(function() {
          //success handler
          console.log('HURRAY')
      }, function() {
          //failed 
          console.log('failed');
      });
      

      【讨论】:

        【解决方案5】:

        如果您喜欢 wilsonpage 的回答但更习惯于使用 async.js 的语法,这里有一个变体:

        function asyncEach(iterableList, callback, done) {
          var i = -1,
              length = iterableList.length;
        
          function loop() {
              i++;
              if (i === length) {
                done(); 
                return;
              }
              callback(iterableList[i], loop);
          } 
          loop();
        }
        
        
        asyncEach(['A', 'B', 'C'], function(item, callback) {
            setTimeout(function(){
            document.write('Iteration ' + item + ' <br>');
            callback();
          }, 1000);
        }, function() {
          document.write('All done!');
        });
        

        可以在这里找到演示 - http://jsfiddle.net/NXTv7/8/

        【讨论】:

          【解决方案6】:

          也看看这个精彩的图书馆caolan / async。您的for 循环可以使用mapSeriesseries 轻松完成。

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

          【讨论】:

            【解决方案7】:

            给定一个异步工作函数someFunction,它将用result 参数回调一个结果函数,说明循环是否应该继续:

            // having:
            // function someFunction(param1, praram2, resultfunc))
            // function done() { alert("For cycle ended"); }
            
            (function(f){ f(f) })(function(f){
              someFunction("param1", "praram2", function(result){
                if (result)
                  f(f); // loop continues
                else
                  done(); // loop ends
              });
            })
            

            为了检查是否结束循环,worker函数someFunction可以将结果函数转发给其他异步操作。另外,通过将函数done作为回调,可以将整个表达式封装成一个异步函数。

            【讨论】:

              【解决方案8】:

              我一直在使用“setTimeout(Func,0);”把戏大约一年。这是我最近写的一些研究来解释如何加快速度。如果您只是想要答案,请跳至第 4 步。第 1 步 2 和 3 解释推理和机制;

              // In Depth Analysis of the setTimeout(Func,0) trick.
              
              //////// setTimeout(Func,0) Step 1 ////////////
              // setTimeout and setInterval impose a minimum 
              // time limit of about 2 to 10 milliseconds.
              
                console.log("start");
                var workCounter=0;
                var WorkHard = function()
                {
                  if(workCounter>=2000) {console.log("done"); return;}
                  workCounter++;
                  setTimeout(WorkHard,0);
                };
              
              // this take about 9 seconds
              // that works out to be about 4.5ms per iteration
              // Now there is a subtle rule here that you can tweak
              // This minimum is counted from the time the setTimeout was executed.
              // THEREFORE:
              
                console.log("start");
                var workCounter=0;
                var WorkHard = function()
                {
                  if(workCounter>=2000) {console.log("done"); return;}
                  setTimeout(WorkHard,0);
                  workCounter++;
                };
              
              // This code is slightly faster because we register the setTimeout
              // a line of code earlier. Actually, the speed difference is immesurable 
              // in this case, but the concept is true. Step 2 shows a measurable example.
              ///////////////////////////////////////////////
              
              
              //////// setTimeout(Func,0) Step 2 ////////////
              // Here is a measurable example of the concept covered in Step 1.
              
                var StartWork = function()
                {
                  console.log("start");
                  var startTime = new Date();
                  var workCounter=0;
                  var sum=0;
                  var WorkHard = function()
                  {
                    if(workCounter>=2000) 
                    {
                      var ms = (new Date()).getTime() - startTime.getTime();
                      console.log("done: sum=" + sum + " time=" + ms + "ms"); 
                      return;
                    }
                    for(var i=0; i<1500000; i++) {sum++;}
                    workCounter++;
                    setTimeout(WorkHard,0);
                  };
                  WorkHard();
                };
              
              // This adds some difficulty to the work instead of just incrementing a number
              // This prints "done: sum=3000000000 time=18809ms".
              // So it took 18.8 seconds.
              
                var StartWork = function()
                {
                  console.log("start");
                  var startTime = new Date();
                  var workCounter=0;
                  var sum=0;
                  var WorkHard = function()
                  {
                    if(workCounter>=2000) 
                    {
                      var ms = (new Date()).getTime() - startTime.getTime();
                      console.log("done: sum=" + sum + " time=" + ms + "ms"); 
                      return;
                    }
                    setTimeout(WorkHard,0);
                    for(var i=0; i<1500000; i++) {sum++;}
                    workCounter++;
                  };
                  WorkHard();
                };
              
              // Now, as we planned, we move the setTimeout to before the difficult part
              // This prints: "done: sum=3000000000 time=12680ms"
              // So it took 12.6 seconds. With a little math, (18.8-12.6)/2000 = 3.1ms
              // We have effectively shaved off 3.1ms of the original 4.5ms of dead time.
              // Assuming some of that time may be attributed to function calls and variable 
              // instantiations, we have eliminated the wait time imposed by setTimeout.
              
              // LESSON LEARNED: If you want to use the setTimeout(Func,0) trick with high 
              // performance in mind, make sure your function takes more than 4.5ms, and set 
              // the next timeout at the start of your function, instead of the end.
              ///////////////////////////////////////////////
              
              
              //////// setTimeout(Func,0) Step 3 ////////////
              // The results of Step 2 are very educational, but it doesn't really tell us how to apply the
              // concept to the real world.  Step 2 says "make sure your function takes more than 4.5ms".
              // No one makes functions that take 4.5ms. Functions either take a few microseconds, 
              // or several seconds, or several minutes. This magic 4.5ms is unattainable.
              
              // To solve the problem, we introduce the concept of "Burn Time".
              // Lets assume that you can break up your difficult function into pieces that take 
              // a few milliseconds or less to complete. Then the concept of Burn Time says, 
              // "crunch several of the individual pieces until we reach 4.5ms, then exit"
              
              // Step 1 shows a function that is asyncronous, but takes 9 seconds to run. In reality
              // we could have easilly incremented workCounter 2000 times in under a millisecond.
              // So, duh, that should not be made asyncronous, its horrible. But what if you don't know
              // how many times you need to increment the number, maybe you need to run the loop 20 times,
              // maybe you need to run the loop 2 billion times.
              
                console.log("start");
                var startTime = new Date();
                var workCounter=0;
                for(var i=0; i<2000000000; i++) // 2 billion
                {
                  workCounter++;
                }
                var ms = (new Date()).getTime() - startTime.getTime();
                console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
              
              // prints: "done: workCounter=2000000000 time=7214ms"
              // So it took 7.2 seconds. Can we break this up into smaller pieces? Yes.
              // I know, this is a retarded example, bear with me.
              
                console.log("start");
                var startTime = new Date();
                var workCounter=0;
                var each = function()
                {
                  workCounter++;
                };
                for(var i=0; i<20000000; i++) // 20 million
                {
                  each();
                }
                var ms = (new Date()).getTime() - startTime.getTime();
                console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
              
              // The easiest way is to break it up into 2 billion smaller pieces, each of which take 
              // only several picoseconds to run. Ok, actually, I am reducing the number from 2 billion
              // to 20 million (100x less).  Just adding a function call increases the complexity of the loop
              // 100 fold. Good lesson for some other topic.
              // prints: "done: workCounter=20000000 time=7648ms"
              // So it took 7.6 seconds, thats a good starting point.
              // Now, lets sprinkle in the async part with the burn concept
              
                console.log("start");
                var startTime = new Date();
                var workCounter=0;
                var index=0;
                var end = 20000000;
                var each = function()
                {
                  workCounter++;
                };
                var Work = function()
                {
                  var burnTimeout = new Date();
                  burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
                  while((new Date()) < burnTimeout)
                  {
                    if(index>=end) 
                    {
                      var ms = (new Date()).getTime() - startTime.getTime();
                      console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
                      return;
                    }
                    each();
                    index++;
                  }
                  setTimeout(Work,0);
                };
              
              // prints "done: workCounter=20000000 time=107119ms"
              // Sweet Jesus, I increased my 7.6 second function to 107.1 seconds.
              // But it does prevent the browser from locking up, So i guess thats a plus.
              // Again, the actual objective here is just to increment workCounter, so the overhead of all
              // the async garbage is huge in comparison. 
              // Anyway, Lets start by taking advice from Step 2 and move the setTimeout above the hard part. 
              
                console.log("start");
                var startTime = new Date();
                var workCounter=0;
                var index=0;
                var end = 20000000;
                var each = function()
                {
                  workCounter++;
                };
                var Work = function()
                {
                  if(index>=end) {return;}
                  setTimeout(Work,0);
                  var burnTimeout = new Date();
                  burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
                  while((new Date()) < burnTimeout)
                  {
                    if(index>=end) 
                    {
                      var ms = (new Date()).getTime() - startTime.getTime();
                      console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
                      return;
                    }
                    each();
                    index++;
                  }
                };
              
              // This means we also have to check index right away because the last iteration will have nothing to do
              // prints "done: workCounter=20000000 time=52892ms"  
              // So, it took 52.8 seconds. Improvement, but way slower than the native 7.6 seconds.
              // The Burn Time is the number you tweak to get a nice balance between native loop speed
              // and browser responsiveness. Lets change it from 4.5ms to 50ms, because we don't really need faster
              // than 50ms gui response.
              
                console.log("start");
                var startTime = new Date();
                var workCounter=0;
                var index=0;
                var end = 20000000;
                var each = function()
                {
                  workCounter++;
                };
                var Work = function()
                {
                  if(index>=end) {return;}
                  setTimeout(Work,0);
                  var burnTimeout = new Date();
                  burnTimeout.setTime(burnTimeout.getTime() + 50); // burnTimeout set to 50ms in the future
                  while((new Date()) < burnTimeout)
                  {
                    if(index>=end) 
                    {
                      var ms = (new Date()).getTime() - startTime.getTime();
                      console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
                      return;
                    }
                    each();
                    index++;
                  }
                };
              
              // prints "done: workCounter=20000000 time=52272ms"
              // So it took 52.2 seconds. No real improvement here which proves that the imposed limits of setTimeout
              // have been eliminated as long as the burn time is anything over 4.5ms
              ///////////////////////////////////////////////
              
              
              //////// setTimeout(Func,0) Step 4 ////////////
              // The performance numbers from Step 3 seem pretty grim, but GUI responsiveness is often worth it.
              // Here is a short library that embodies these concepts and gives a descent interface.
              
                var WilkesAsyncBurn = function()
                {
                  var Now = function() {return (new Date());};
                  var CreateFutureDate = function(milliseconds)
                  {
                    var t = Now();
                    t.setTime(t.getTime() + milliseconds);
                    return t;
                  };
                  var For = function(start, end, eachCallback, finalCallback, msBurnTime)
                  {
                    var i = start;
                    var Each = function()
                    {
                      if(i==-1) {return;} //always does one last each with nothing to do
                      setTimeout(Each,0);
                      var burnTimeout = CreateFutureDate(msBurnTime);
                      while(Now() < burnTimeout)
                      {
                        if(i>=end) {i=-1; finalCallback(); return;}
                        eachCallback(i);
                        i++;
                      }
                    };
                    Each();
                  };
                  var ForEach = function(array, eachCallback, finalCallback, msBurnTime)
                  {
                    var i = 0;
                    var len = array.length;
                    var Each = function()
                    {
                      if(i==-1) {return;}
                      setTimeout(Each,0);
                      var burnTimeout = CreateFutureDate(msBurnTime);
                      while(Now() < burnTimeout)
                      {
                        if(i>=len) {i=-1; finalCallback(array); return;}
                        eachCallback(i, array[i]);
                        i++;
                      }
                    };
                    Each();
                  };
              
                  var pub = {};
                  pub.For = For;          //eachCallback(index); finalCallback();
                  pub.ForEach = ForEach;  //eachCallback(index,value); finalCallback(array);
                  WilkesAsyncBurn = pub;
                };
              
              ///////////////////////////////////////////////
              
              
              //////// setTimeout(Func,0) Step 5 ////////////
              // Here is an examples of how to use the library from Step 4.
              
                WilkesAsyncBurn(); // Init the library
                console.log("start");
                var startTime = new Date();
                var workCounter=0;
                var FuncEach = function()
                {
                  if(workCounter%1000==0)
                  {
                    var s = "<div></div>";
                    var div = jQuery("*[class~=r1]");
                    div.append(s);
                  }
                  workCounter++;
                };
                var FuncFinal = function()
                {
                  var ms = (new Date()).getTime() - startTime.getTime();
                  console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
                };
                WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50);
              
              // prints: "done: workCounter=20000000 time=149303ms"
              // Also appends a few thousand divs to the html page, about 20 at a time.
              // The browser is responsive the entire time, mission accomplished
              
              // LESSON LEARNED: If your code pieces are super tiny, like incrementing a number, or walking through 
              // an array summing the numbers, then just putting it in an "each" function is going to kill you. 
              // You can still use the concept here, but your "each" function should also have a for loop in it 
              // where you burn a few hundred items manually.  
              ///////////////////////////////////////////////
              

              【讨论】:

                【解决方案9】:

                我们也可以使用 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)}}
                    ]
                  })
                }
                

                【讨论】:

                  【解决方案10】:

                  http://cuzztuts.blogspot.ro/2011/12/js-async-for-very-cool.html

                  编辑:

                  来自 github 的链接:https://github.com/cuzzea/lib_repo/blob/master/cuzzea/js/functions/core/async_for.js

                  function async_for_each(object,settings){
                  var l=object.length;
                      settings.limit = settings.limit || Math.round(l/100);
                      settings.start = settings.start || 0;
                      settings.timeout = settings.timeout || 1;
                      for(var i=settings.start;i<l;i++){
                          if(i-settings.start>=settings.limit){
                              setTimeout(function(){
                                  settings.start = i;
                                  async_for_each(object,settings)
                              },settings.timeout);
                              settings.limit_callback ? settings.limit_callback(i,l) : null;
                              return false;
                          }else{
                              settings.cbk ? settings.cbk(i,object[i]) : null;
                          }
                      }
                      settings.end_cbk?settings.end_cbk():null;
                      return true;
                  }
                  

                  此函数允许您使用 settings.limit 在 for 循环中创建百分比中断。 limit 属性只是一个整数,但是当设置为 array.length * 0.1 时,这将使 settings.limit_callback 每 10% 被调用一次。

                  /*
                   * params:
                   *  object:         the array to parse
                   *  settings_object:
                   *      cbk:            function to call whenwhen object is found in array
                   *                          params: i,object[i]
                   *      limit_calback:  function to call when limit is reached
                   *                          params: i, object_length
                   *      end_cbk:        function to call when loop is finished
                   *                          params: none
                   *      limit:          number of iteration before breacking the for loop
                   *                          default: object.length/100
                   *      timeout:        time until start of the for loop(ms)
                   *                          default: 1
                   *      start:          the index from where to start the for loop
                   *                          default: 0
                   */
                  

                  示例:

                  var a = [];
                  a.length = 1000;
                  async_for_each(a,{
                      limit_callback:function(i,l){console.log("loading %s/%s - %s%",i,l,Math.round(i*100/l))}
                  });
                  

                  【讨论】:

                    【解决方案11】:

                    @Ivo 建议的更简洁的替代方案是异步方法队列,假设您只需要对集合进行一次异步调用。

                    (有关更详细的解释,请参阅 Dustin Diaz 的 this post

                    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 将匿名函数放入队列而不是立即执行,然后在异步调用完成时刷新队列。这是一个非常简单而强大的设计模式。

                    附:如果您使用的是 jQuery,那么您已经有一个名为 jQuery.Deferred 的异步方法队列供您使用。

                    【讨论】:

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

                    我简化了这个:

                    功能:

                    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 部分放在库函数中。
                    • 基本上不是递归吗?
                    【解决方案13】:

                    如果你阻止脚本,你不能在 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
                    

                    【讨论】:

                    • 也许我遗漏了一些东西,但我不明白这实际上是异步的。你不需要 setTimeout 什么的吗?我尝试了你的代码,取出了 console.log,然后把计数调高了很多,它只是冻结了浏览器。
                    • 我对@9​​87654325@ 的用途感到困惑?如果您愿意,只是一种强制退出的方法?
                    • 就像上面说的rocketsarefast,这个答案不是异步的,因此完全错误!
                    • 像 loop.next 一样,当 done 为 true 时,loop.break 应该是一个空操作:` break: function() { if (!done) { done = true;打回来(); } } `
                    • 抱歉,由于这实际上不是异步的,因此不得不投反对票。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-06-29
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多