Appel d'une fonction jQuery après la fin de .each ()

184

Dans jQuery, est-il possible d' appeler un rappel ou de déclencher un événement après qu'un appel de .each()(ou de tout autre type de rappel itératif) soit terminé .

Par exemple, je voudrais que cette "fondu et suppression" se termine

$(parentSelect).nextAll().fadeOut(200, function() {
    $(this).remove();
});

avant de faire quelques calculs et d'insérer de nouveaux éléments après le $(parentSelect). Mes calculs sont incorrects si les éléments existants sont toujours visibles par jQuery et que dormir / retarder une durée arbitraire (200 pour chaque élément) semble au mieux être une solution fragile.

Je peux facilement .bind()la logique nécessaire à un rappel d'événement , mais je ne sais pas comment proprement Invoke l' .trigger()après ce qui précède itération terminée . De toute évidence, je ne peux pas invoquer le déclencheur à l'intérieur de l'itération car il se déclencherait plusieurs fois.

Dans le cas de $.each(), j'ai envisagé d'ajouter quelque chose à la fin de l'argument de données (que je rechercherais manuellement dans le corps de l'itération) mais je détesterais être forcé à cela, alors j'espérais qu'il y avait un autre élégant moyen de contrôler le flux par rapport aux rappels itératifs.

Luther Baker
la source
1
Ai-je bien compris que ce n'est pas tant le ".each ()" lui-même que vous voulez terminer, mais plutôt toutes les animations lancées par l'appel ".each ()"?
Pointy
C'est un bon point. Avec cette question particulière, oui, je suis principalement préoccupé par l'achèvement de ".each ()" lui-même ... mais je pense que vous soulevez une autre question viable.
Luther Baker
Il existe un package léger pour ce github.com/ACFBentveld/Await
Wim Pruiksma

Réponses:

164

Une alternative à la réponse de @ tv:

var elems = $(parentSelect).nextAll(), count = elems.length;

elems.each( function(i) {
  $(this).fadeOut(200, function() { 
    $(this).remove(); 
    if (!--count) doMyThing();
  });
});

Notez qu'elle .each()est elle - même synchrone - l'instruction qui suit l'appel à .each()ne sera exécutée qu'une fois l' .each()appel terminé. Cependant, les opérations asynchrones lancées dans l' .each()itération se poursuivront bien sûr à leur manière. C'est le problème ici: les appels à atténuer les éléments sont des animations pilotées par le minuteur, et celles-ci continuent à leur propre rythme.

La solution ci-dessus, par conséquent, garde une trace du nombre d'éléments estompés. Chaque appel à .fadeOut()reçoit un rappel d'achèvement. Lorsque le rappel remarque qu'il est compté à travers tous les éléments d'origine impliqués, une action ultérieure peut être entreprise avec la certitude que tout l'évanouissement est terminé.

C'est une réponse vieille de quatre ans (à ce stade en 2014). Une manière moderne de faire cela impliquerait probablement d'utiliser le mécanisme Différé / Promise, bien que ce qui précède soit simple et devrait fonctionner correctement.

Pointu
la source
4
J'aime ce changement, même si je ferais la comparaison à 0 au lieu de la négation logique (--count == 0) puisque vous comptez à rebours. Pour moi, cela rend l'intention plus claire, bien que cela ait le même effet.
tvanfosson
Il s'avère que votre commentaire initial sur la question était parfait. Je demandais à propos de .each () et j'ai pensé que c'était ce que je voulais, mais comme vous et tvanfosson et maintenant patrick l'ont souligné - c'était le fondu final qui m'intéressait réellement. Je pense que nous sommes tous d'accord pour dire que votre exemple (en comptant à la place des index) est probablement le plus sûr.
Luther Baker
@ A.DIMO Que voulez-vous dire par «trop compliqué»? Quelle partie est compliquée?
Pointy
175

Il est probablement trop tard mais je pense que ce code fonctionne ...

$blocks.each(function(i, elm) {
 $(elm).fadeOut(200, function() {
  $(elm).remove();
 });
}).promise().done( function(){ alert("All was done"); } );
Sébastien GRAVIER
la source
156

Ok, c'est peut-être un peu après coup, mais .promise () devrait également réaliser ce que vous recherchez.

Documentation de la promesse

Un exemple d'un projet sur lequel je travaille:

$( '.panel' )
    .fadeOut( 'slow')
    .promise()
    .done( function() {
        $( '#' + target_panel ).fadeIn( 'slow', function() {});
    });

:)

nbsp
la source
9
.promise () n'est pas disponible sur .each ()
Michael Fever
26

JavaScript s'exécute de manière synchrone, donc tout ce que vous placez après each()ne fonctionnera pas tant qu'il ne sera pas each()terminé.

Considérez le test suivant:

var count = 0;
var array = [];

// populate an array with 1,000,000 entries
for(var i = 0; i < 1000000; i++) {
    array.push(i);
}

// use each to iterate over the array, incrementing count each time
$.each(array, function() {
    count++
});

// the alert won't get called until the 'each' is done
//      as evidenced by the value of count
alert(count);

Lorsque l'alerte est appelée, le nombre sera égal à 1000000 car l'alerte ne s'exécutera pas tant qu'elle ne sera pas each()terminée.

utilisateur113716
la source
8
Le problème dans l'exemple donné est que le fadeOut est placé dans la file d'attente d'animation et ne s'exécute pas de manière synchrone. Si vous utilisez eachet planifiez chaque animation séparément, vous devez toujours déclencher l'événement une fois l'animation terminée, et non chacune.
tvanfosson
3
@tvanfosson - Ouais, je sais. Selon ce que Luther veut accomplir, votre solution (et celle de Pointy) semble être la bonne approche. Mais d'après les commentaires de Luther comme ... Naïvement, je cherche quelque chose comme elems.each (foo, bar) où foo = 'fonction appliquée à chaque élément' et bar = 'callback après que toutes les itérations sont terminées'. ... il semble insister sur le fait que c'est un each()problème. C'est pourquoi j'ai jeté cette réponse dans le mélange.
user113716
Vous avez raison Patrick. J'ai (mal) compris que .each () planifiait ou mettait en file d'attente mes rappels. Je pense qu'invoquer fadeOut m'a indirectement conduit à cette conclusion. tvanfosson et Pointy ont tous deux fourni des réponses au problème de fadeOut (ce que je recherchais vraiment), mais votre message corrige un peu ma compréhension. Votre réponse répond en fait le mieux à la question initiale, comme indiqué, tandis que Pointy et tvanfosson ont répondu à la question que j'essayais de poser. J'aimerais pouvoir choisir deux réponses. Merci d'avoir 'jeté cette réponse dans le mélange' :)
Luther Baker
@ user113716 Je pensais que les fonctions à l'intérieur eachn'étaient pas garanties d'être appelées auparavant alert. pourriez-vous m'expliquer ou me montrer la lecture à ce sujet?
Maciej Jankowski
5

J'ai trouvé beaucoup de réponses traitant des tableaux mais pas d'un objet json. Ma solution consistait simplement à parcourir l'objet une fois tout en incrémentant un compteur, puis lors de l'itération à travers l'objet pour exécuter votre code, vous pouvez incrémenter un deuxième compteur. Ensuite, vous comparez simplement les deux compteurs ensemble et obtenez votre solution. Je sais que c'est un peu maladroit mais je n'ai pas encore trouvé de solution plus élégante. Voici mon exemple de code:

var flag1 = flag2 = 0;

$.each( object, function ( i, v ) { flag1++; });

$.each( object, function ( ky, val ) {

     /*
        Your code here
     */
     flag2++;
});

if(flag1 === flag2) {
   your function to call at the end of the iteration
}

Comme je l'ai dit, ce n'est pas le plus élégant, mais cela fonctionne et cela fonctionne bien et je n'ai pas encore trouvé de meilleure solution.

Salutations, JP

JimP
la source
1
Non, cela ne fonctionne pas. Il déclenche tous les .each, puis la dernière chose et tout fonctionne en même temps. Essayez de mettre un setTimeout sur votre .each et vous verrez qu'il exécute juste le drapeau1 === flag2 tout de suite
Michael Fever
4

J'utilise quelque chose comme ça:

$.when(
           $.each(yourArray, function (key, value) {
                // Do Something in loop here
            })
          ).then(function () {
               // After loop ends.
          });
Softmixt
la source
1

Si vous êtes prêt à faire quelques étapes, cela pourrait fonctionner. Cela dépend cependant de la fin des animations dans l'ordre. Je ne pense pas que cela devrait être un problème.

var elems = $(parentSelect).nextAll();
var lastID = elems.length - 1;

elems.each( function(i) {
    $(this).fadeOut(200, function() { 
        $(this).remove(); 
        if (i == lastID) {
           doMyThing();
        }
    });
});
Tvanfosson
la source
Cela fonctionnerait - mais pas ce que j'espérais. Naïvement, je cherche quelque chose comme elems.each (foo, bar) où foo = 'fonction appliquée à chaque élément' et bar = 'callback après que toutes les itérations sont terminées'. Je vais donner un peu plus de temps à la question pour voir si nous rencontrons un coin sombre de jQuery :)
Luther Baker
AFAIK - vous devez le déclencher lorsque la "dernière" animation se termine et comme ils s'exécutent de manière asynchrone, vous devez le faire dans le rappel de l'animation. J'aime mieux la version de @ Pointy de ce que j'ai écrit car elle ne dépend pas de la commande, non pas que je pense que ce serait un problème.
tvanfosson
0

qu'en est-il de

$(parentSelect).nextAll().fadeOut(200, function() { 
    $(this).remove(); 
}).one(function(){
    myfunction();
}); 
Mark Schultheiss
la source
Cela appelle définitivement myfunction () une fois (et m'introduit dans un autre concept jQuery) mais ce que je cherchais était une garantie de «quand» il fonctionnerait - pas simplement qu'il fonctionnerait une fois. Et, comme le mentionne Patrick, cette partie s'avère assez facile. Ce que je cherchais vraiment, c'était un moyen d'invoquer quelque chose après la dernière logique de fadeOut, ce qui, je ne pense pas, garantit .one ().
Luther Baker
Je pense que la chaîne fait cela - puisque la première partie passe avant la dernière partie.
Mark Schultheiss
0

Vous devez mettre le reste de votre demande en file d'attente pour que cela fonctionne.

var elems = $(parentSelect).nextAll();
var lastID = elems.length - 1;

elems.each( function(i) {
    $(this).fadeOut(200, function() { 
        $(this).remove(); 
        if (i == lastID) {
            $j(this).queue("fx",function(){ doMyThing;});
        }
    });
});
Elyx0
la source
0

Je rencontre le même problème et je l'ai résolu avec une solution comme le code suivant:

var drfs = new Array();
var external = $.Deferred();
drfs.push(external.promise());

$('itemSelector').each( function() {
    //initialize the context for each cycle
    var t = this; // optional
    var internal = $.Deferred();

    // after the previous deferred operation has been resolved
    drfs.pop().then( function() {

        // do stuff of the cycle, optionally using t as this
        var result; //boolean set by the stuff

        if ( result ) {
            internal.resolve();
        } else {
            internal.reject();
        }
    }
    drfs.push(internal.promise());
});

external.resolve("done");

$.when(drfs).then( function() {
    // after all each are resolved

});

La solution résout le problème suivant: pour synchroniser les opérations asynchrones lancées dans l'itération .each (), à l'aide de l'objet Deferred.

Roberto AGOSTINO
la source
Il vous manque un crochet fermant) avant: drfs.push (internal.promise ()); au moins je pense que c'est avant ..
Michael Fever
0

Peut-être une réponse tardive, mais il existe un package pour gérer cela https://github.com/ACFBentveld/Await

 var myObject = { // or your array
        1 : 'My first item',
        2 : 'My second item',
        3 : 'My third item'
    }

    Await.each(myObject, function(key, value){
         //your logic here
    });

    Await.done(function(){
        console.log('The loop is completely done');
    });
Wim Pruiksma
la source