Асинхронний для циклу в JavaScript


87

Мені потрібен цикл, який чекає асинхронного виклику, перш ніж продовжувати. Щось на зразок:

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

Як я міг це зробити? У вас є ідеї?


128
Вау ( /* ... */ )схожа на монстра, і я зараз боюся :(
Точний

Відповіді:


182

Ви не можете змішувати синхронне та асинхронне в JavaScript, якщо ви блокуєте сценарій, ви блокуєте браузер.

Вам потрібно пройти повний сюжетний шлях сюди, на щастя, ми можемо приховати потворні речі.

EDIT: оновлено код.

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

28
можливо мені чогось не вистачає, але я не розумію, наскільки це насправді асинхронно. Вам не потрібен setTimeout чи щось інше? Я спробував ваш код, вийняв console.log і багато зробив підрахунок, і це просто заморожує браузер.
racketsarefast

Мене бентежить, що loop.break()потрібно робити? Просто спосіб вигнати, якщо хочете?
whitfin

2
як сказано вище rocketsarefast, ця відповідь не є асинхронною і тому абсолютно неправильною!
kofifus

як loop.next, loop.break слід робити не-операцією, коли зроблено істинно: `break: function () {if (! done) {done = true; зворотний виклик (); }} `
db-inf

4
Вибачте, мені довелося проголосувати проти того, що це насправді не є асинхронним.
Рікаел

44

Я спростив це:

ФУНКЦІЯ:

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 я помістив у функцію бібліотеки.
racketsarefast

Хіба це не рекурсія?
Павло

7

Більш чистою альтернативою запропонованому @Ivo буде Черга асинхронних методів , припускаючи, що вам потрібно зробити лише один асинхронний виклик для колекції.

(Дивіться цю публікацію Дастіна Діаза для більш детального пояснення)

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 .


1
Ну, якщо правильно зрозуміти питання, це не дасть бажаної поведінки, здається, що вона хоче зробити кілька зворотних викликів, при someFunctionяких затримується решта циклу, ваш шаблон встановлює список функцій, які будуть виконуватися в порядку і всі отримають результати одного іншого виклику функції. Це хороший шаблон, але я не думаю, що він відповідає питанню, про яке йдеться.
Ivo Wetzel

@Ivo Без додаткової інформації ми точно не будемо знати, але, загалом кажучи, я вважаю поганим дизайном змушувати синхронний код чекати на асинхронну операцію, перш ніж продовжувати; у кожному випадку, який я спробував, це призвело до помітного відставання користувацького інтерфейсу через JS однопотоковість. Якщо операція триває занадто довго, ви ризикуєте примусово зупинити ваш сценарій браузером.
Адам Лассек,

@Ivo також я дуже обережно ставлюсь до коду, на який покладається setTimeout. Ви ризикуєте ненавмисною поведінкою, якщо код виконується швидше, ніж ви передбачаєте.
Адам Лассек,

@Adam. Яким чином я ризикнув би ненавмисною поведінкою, setTimeoutщо, якщо зворотний виклик займе лише половину часу, ну а код виконується швидше знову ... так, що толку? "Код" у "циклі" все ще в порядку, якщо ви робите щось поза ним до повного зворотного виклику, який ви вже закликаєте до неприємностей, але знову ж таки він є одним потоком, мені важко придумати сценарій, коли setTimeoutщось зламається, без подальшого неправильного проектування.
Ivo Wetzel

Крім того, він запитав такий модуль Node.js в іншому питанні, я заявив, що це взагалі погана ідея мати загальне рішення для таких циклів "async-sync". Я волів би піти з чимось, що відповідає точним вимогам того, що я намагаюся досягти.
Ivo Wetzel

3

Також подивіться на цю чудову бібліотеку caolan / async . Ваш forцикл можна легко здійснити за допомогою mapSeries або series .

Я міг би опублікувати зразок коду, якби у вашому прикладі було більше деталей.


2

Ми також можемо скористатися допомогою 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)}}
    ]
  })
}

2

Я використовую "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.  
///////////////////////////////////////////////

2

Дано асинхронну робочу функцію, 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
  });
})

Для того, щоб перевірити, закінчувати цикл чи ні, робоча функція someFunctionможе переслати функцію результату до інших асинхронних операцій. Крім того, весь вираз можна інкапсулювати в асинхронну функцію, беручи функцію doneяк зворотний виклик.


1

Якщо вам подобається відповідь 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/


1

Ось ще один приклад, який, на мою думку, є більш читабельним, ніж інші, де ви обертаєте свою функцію асинхронування всередині функції, яка приймає doneфункцію, індекс поточного циклу та результат (якщо такий є) попереднього виклику асинхронізації:

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

Після done()виклику він запускає наступний асинхронний виклик, знову передаючи виконану функцію, поточний індекс та попередній результат. Після завершення всього циклу callbackбуде викликаний наданий цикл .

Ось фрагмент, який ви можете запустити:

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
}


1

Ви можете використовувати async awaitвведені в ES7:

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

Це працює, лише якщо someFunctionповертається Обіцянка!

Якщо someFunctionне повертає обіцянку, ви можете зробити це, щоб повернути обіцянку самостійно, як це:

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

Потім замініть цей рядок await someFunction(param1, param2);наawait asynSomeFunction(param1, param2);

Будь ласка, зрозумійте Promises перед написанням async awaitкоду!


Це повинно дати Unexpected await inside loop.
Рейра

@Reyraa, що не є javascriptпроблемою. Це попередження надходить із вашої eslintконфігурації. Я завжди відключаю це правило, eslintтому що в більшості місць, які мені справді потрібні, чекають всередині циклу
Praveena

0

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

Ця функція дозволяє створити відсоток обриву циклу for за допомогою settings.limit. Властивість 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))}
});

0

Рішення на основі обіцяної бібліотеки:

/*
    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');
});

0

Мені потрібно було викликати кілька Xразів асинхронних функцій , кожна ітерація повинна була відбутися після того, як була зроблена попередня, тому я написав бібліотеку litte, яку можна використовувати так:

// 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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.