Asynchrone pour cycle en JavaScript

87

J'ai besoin d'une boucle qui attend un appel asynchrone avant de continuer. Quelque chose comme:

for ( /* ... */ ) {

  someFunction(param1, praram2, function(result) {

    // Okay, for cycle could continue

  })

}

alert("For cycle ended");

Comment pourrais-je faire ça? As tu des idées?

Marry Hoffser
la source
128
Wow ( /* ... */ )ressemble à un monstre et j'ai peur maintenant :(
Pointy

Réponses:

182

Vous ne pouvez pas mélanger synchrone et asynchrone en JavaScript si vous bloquez le script, vous bloquez le navigateur.

Vous devez suivre le chemin complet de l'événement ici, heureusement, nous pouvons cacher les trucs laids.

EDIT: mise à jour du code.

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

Cela nous fournira une fonction asynchrone loop, vous pourrez bien sûr la modifier encore plus pour prendre par exemple une fonction pour vérifier la condition de la boucle etc.

Passons maintenant au test:

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

Et la sortie:

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
Ivo Wetzel
la source
28
peut-être me manque-t-il quelque chose, mais je ne comprends pas en quoi c'est asyncrone. Vous n'avez pas besoin d'un setTimeout ou de quelque chose? J'ai essayé votre code, j'ai sorti le console.log, et j'ai beaucoup augmenté le nombre et cela gèle simplement le navigateur.
rocketsarefast
Je ne sais pas ce que je veux loop.break()faire? Juste un moyen de forcer si tu veux?
whitfin
2
comme le dit rocketsarefast ci-dessus, cette réponse n'est pas asyncrone et donc complètement fausse!
kofifus du
comme loop.next, loop.break devrait devenir un no-op quand done est vrai: `break: function () {if (! done) {done = true; rappeler(); }} `
db-inf
4
Désolé, j'ai dû voter parce que cela n'était pas réellement asynchrone.
Rikaelus
44

J'ai simplifié ceci:

FONCTION:

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

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

USAGE:

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

EXEMPLE: http://jsfiddle.net/NXTv7/8/

Wilsonpage
la source
+1 J'ai fait quelque chose de similaire, mais j'ai mis la partie setTimeout dans la fonction de bibliothèque.
rocketsarefast
N'est-ce pas fondamentalement une récursivité?
Pavel
7

Une alternative plus propre à ce que @Ivo a suggéré serait une file d'attente de méthodes asynchrones , en supposant que vous ne devez effectuer qu'un seul appel asynchrone pour la collection.

(Voir cet article de Dustin Diaz pour une explication plus détaillée)

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

Vous créez simplement une nouvelle instance de Queue, ajoutez les rappels dont vous avez besoin, puis videz la file d'attente avec la réponse asynchrone.

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

Un avantage supplémentaire de ce modèle est que vous pouvez ajouter plusieurs fonctions à la file d'attente au lieu d'une seule.

Si vous avez un objet qui contient des fonctions d'itérateur, vous pouvez ajouter la prise en charge de cette file d'attente en arrière-plan et écrire du code qui semble synchrone, mais qui ne l'est pas:

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

écrivez simplement eachpour mettre la fonction anonyme dans la file d'attente au lieu de l'exécuter immédiatement, puis videz la file d'attente lorsque votre appel asynchrone est terminé. Il s'agit d'un modèle de conception très simple et puissant.

PS Si vous utilisez jQuery, vous disposez déjà d'une file d'attente de méthode asynchrone appelée jQuery.Deferred .

Adam Lassek
la source
1
Eh bien, si someFunctionvous avez bien compris la question cela ne donnera pas le comportement souhaité, il semble qu'elle veuille faire des rappels dans lesquels retarder le reste de la boucle, votre modèle met en place une liste de fonctions qui seront exécutées dans l'ordre et recevront toutes les résultats d' un autre appel de fonction. C'est un bon schéma mais je ne pense pas qu'il corresponde à la question en question.
Ivo Wetzel
@Ivo Sans plus d'informations, nous ne saurons pas avec certitude, mais pour parler en général, je pense que c'est une mauvaise conception de faire attendre le code synchrone pour une opération asynchrone avant de continuer; dans tous les cas où je l'ai essayé, cela a entraîné un décalage notable de l'interface utilisateur en raison du fait que JS était monothread. Si l'opération prend trop de temps, vous courez le risque que votre script soit arrêté de force par le navigateur.
Adam Lassek
@Ivo aussi je me méfie beaucoup du code qui repose sur setTimeout. Vous risquez un comportement involontaire si le code est exécuté plus rapidement que prévu.
Adam Lassek
@Adam De quelle manière risquerais-je un comportement involontaire avec et setTimeoutsi le rappel ne prend que la moitié du temps, eh bien le code s'exécute à nouveau plus rapidement ... alors à quoi ça sert ? Le "code" dans la "boucle" est toujours en ordre, si vous faites des choses en dehors de celui-ci avant le rappel complet, vous appelez déjà pour un problème, mais là encore c'est un thread unique, j'ai du mal à trouver un scénario où setTimeoutcasserait quelque chose, sans autre mauvaise conception.
Ivo Wetzel
De plus, il a demandé un module Node.js comme celui-ci dans une autre question, j'ai dit que c'était en général une mauvaise idée d'avoir une solution générique pour de telles boucles "async-sync". Je préfère opter pour quelque chose qui correspond aux exigences exactes de tout ce que j'essaie de réaliser.
Ivo Wetzel
3

Regardez aussi cette splendide bibliothèque caolan / async . Votre forboucle peut facilement être réalisée en utilisant mapSeries ou series .

Je pourrais publier un exemple de code si votre exemple contenait plus de détails.

Mrchief
la source
2

Nous pouvons également utiliser l'aide de jquery.Deferred. dans ce cas, la fonction asyncLoop ressemblerait à ceci:

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

la fonction de rappel ressemblera à ceci:

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

exemple de fonction qui résout différé:

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)}}
    ]
  })
}
Vladyslav Goloshchapov
la source
2

J'utilise le "setTimeout (Func, 0);" truc pendant environ un an. Voici quelques recherches récentes que j'ai rédigées pour expliquer comment l'accélérer un peu. Si vous voulez juste la réponse, passez à l'étape 4. Les étapes 1 2 et 3 expliquent le raisonnement et la mécanique;

// 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.  
///////////////////////////////////////////////
fusée rapide
la source
2

Étant donné une fonction de travail asynchrone someFunctionqui rappellera une fonction de résultat avec un resultargument indiquant si la boucle doit continuer ou non:

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

Afin de vérifier s'il faut ou non terminer la boucle, la fonction de travail someFunctionpeut transmettre la fonction de résultat à d'autres opérations asynchrones. En outre, l'expression entière peut être encapsulée dans une fonction asynchrone en prenant une fonction donecomme rappel.

Sébastien
la source
1

Si vous aimez la réponse de wilsonpage mais que vous êtes plus habitué à utiliser la syntaxe de async.js, voici une variante:

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

La démo peut être trouvée ici - http://jsfiddle.net/NXTv7/8/

jczaplew
la source
1

Voici un autre exemple qui, à mon avis, est plus lisible que d'autres, où vous enveloppez votre fonction asynchrone dans une fonction qui prend une donefonction, l'index de la boucle actuelle et le résultat (le cas échéant) de l'appel async précédent:

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

Une fois done()invoqué, il déclenche le prochain appel asynchrone, en passant à nouveau la fonction done, l'index actuel et le résultat précédent. Une fois la boucle entière terminée, la boucle fournie callbacksera appelée.

Voici un extrait que vous pouvez exécuter:

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
}

shimizu
la source
1

Vous pouvez utiliser async awaitintroduit dans ES7:

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

Cela ne fonctionne que si someFunction retournez une promesse!

Si someFunction ne retournez pas une promesse, vous pouvez lui faire renvoyer une promesse par vous-même comme ceci:

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

Puis remplacez cette ligne await someFunction(param1, param2); parawait asynSomeFunction(param1, param2);

Veuillez comprendre les promesses avant d'écrire du async awaitcode!

Praveena
la source
Cela devrait donner Unexpected await inside loop.
Reyraa
@Reyraa ce n'est pas un javascriptproblème. Cet avertissement provient de votre eslintconfiguration. Je désactive toujours cette règle eslintparce que dans la plupart des endroits où j'ai vraiment besoin d'attendre dans la boucle
Praveena
0

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

ÉDITER:

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

Cette fonction vous permet de créer un pourcentage de rupture dans la boucle for en utilisant settings.limit. La propriété limit est juste un entier, mais lorsqu'elle est définie comme array.length * 0.1, cela rendra le settings.limit_callback appelé tous les 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
 */

exemple:

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))}
});
cuzzea
la source
0

Une solution basée sur une bibliothèque de promesses:

/*
    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');
});
tyskr
la source
0

J'avais besoin d'appeler des Xheures de fonction asynchrones , chaque itération devait avoir lieu après la précédente, alors j'ai écrit un petite bibliothèque qui peut être utilisée comme ceci:

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

Chaque fois qu'une fonction de boucle définie par l'utilisateur est appelée, elle a deux arguments, un index d'itération et une valeur de retour de l'appel précédent.

Voici un exemple de sortie:

"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"
Buksy
la source