87 votes

Cycle de for asynchrone en JavaScript

J'ai besoin d'une boucle qui attende 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 ai-je pu faire ça ? Avez-vous des idées ?

128 votes

Wow ( /* ... */ ) ressemble à un monstre et j'ai peur maintenant :(

182voto

Ivo Wetzel Points 27802

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

Il faut aller jusqu'au bout de la démarche événementielle ici, heureusement on peut cacher les trucs moches.

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 permettra d'avoir un système asynchrone loop Vous pouvez bien sûr le 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 le résultat :

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 votes

Peut-être que quelque chose m'échappe, mais je ne comprends pas comment cela peut être asynchrone. N'avez-vous pas besoin d'un setTimeout ou autre ? J'ai essayé votre code, j'ai enlevé le console.log, et j'ai beaucoup augmenté le compte et ça ne fait que geler le navigateur.

0 votes

Je suis confus quant à ce que loop.break() est censé faire ? Juste un moyen de forcer la sortie si vous le souhaitez ?

2 votes

Comme rocketsarefast le dit plus haut, cette réponse n'est pas asynchrone et donc complètement fausse !

44voto

wilsonpage Points 5495

Je l'ai simplifié :

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
}

UTILISATION :

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/

0 votes

+1 J'ai fait quelque chose de similaire à ceci, mais j'ai mis la partie setTimeout dans la fonction de la bibliothèque.

0 votes

N'est-ce pas en fait une récursion ?

7voto

Adam Lassek Points 18918

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 faire qu'un seul appel asynchrone pour la collection.

(Voir ce poste par 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);

Il suffit de créer une nouvelle instance de Queue ajoutez les callbacks 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 le support de cette file d'attente en coulisse et écrire du code qui semble synchrone, mais ne l'est pas :

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

écrire simplement each pour placer la fonction anonyme dans la file d'attente au lieu de l'exécuter immédiatement, et ensuite vider la file d'attente lorsque votre appel asynchrone est terminé. Il s'agit d'un modèle de conception très simple et très puissant.

P.S. Si vous utilisez jQuery, vous disposez déjà d'une file d'attente de méthodes asynchrones appelée jQuery.Deferred .

1 votes

Si j'ai bien compris la question, cela ne produira pas le comportement souhaité. Il semble qu'elle veuille faire des rappels dans le fichier someFunction qui retarde le reste de la boucle, votre modèle établit une liste de fonctions qui seront exécutées dans l'ordre et recevront toutes les résultats de la fonction un autre appel de fonction. C'est un bon modèle mais je ne pense pas qu'il corresponde à la question posée.

0 votes

@Ivo Sans plus d'informations, nous ne pourrons pas en être sûrs, mais en parlant en général, je pense que c'est une mauvaise conception de faire en sorte que le code synchrone attende 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 est monofil. Si l'opération prend trop de temps, vous courez le risque que votre script soit arrêté de force par le navigateur.

0 votes

@Ivo aussi je me méfie beaucoup du code qui repose sur des setTimeout . Vous risquez un comportement involontaire si le code est exécuté plus rapidement que prévu.

3voto

Mrchief Points 25418

Regardez aussi cette splendide bibliothèque caolan / async . Votre for peut facilement être réalisée en utilisant mapSeries o série .

Je pourrais poster un exemple de code si votre exemple était plus détaillé.

2voto

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 les différés :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X