Aufrufen einer jQuery-Funktion nach Abschluss von .each ()

182

In jQuery ist es möglich, einen Rückruf aufzurufen oder ein Ereignis auszulösen, nachdem ein Aufruf von .each()(oder einer anderen Art von iterativem Rückruf) abgeschlossen wurde .

Zum Beispiel möchte ich, dass dieses "Ausblenden und Entfernen" abgeschlossen wird

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

bevor Sie einige Berechnungen durchführen und neue Elemente nach dem einfügen $(parentSelect). Meine Berechnungen sind falsch, wenn die vorhandenen Elemente für jQuery noch sichtbar sind und das Schlafen / Verzögern einer beliebigen Zeitspanne (200 für jedes Element) bestenfalls als spröde Lösung erscheint.

Ich kann leicht .bind()die notwendige Logik zu einem Ereignisse Rückruf , aber ich bin nicht sicher , wie invoke die sauber .trigger()nach dem obigen Iteration abgeschlossen . Offensichtlich kann ich den Trigger innerhalb der Iteration nicht aufrufen, da er mehrmals ausgelöst würde.

Im Fall von $.each()habe ich darüber nachgedacht, am Ende des Datenarguments etwas hinzuzufügen (nach dem ich im Iterationskörper manuell gesucht habe), aber ich würde es hassen, dazu gezwungen zu werden, also hoffte ich, dass es noch etwas anderes Elegantes gibt Möglichkeit, den Fluss in Bezug auf iterative Rückrufe zu steuern.

Luther Baker
quelle
1
Verstehe ich richtig, dass Sie nicht so sehr ".each ()" selbst beenden möchten, sondern alle Animationen, die durch den Aufruf von ".each ()" gestartet wurden?
Pointy
Das ist ein guter Punkt. Ja, mit dieser speziellen Frage bin ich hauptsächlich besorgt über die Vervollständigung von ".each ()" selbst ... aber ich denke, Sie werfen eine andere tragfähige Frage auf.
Luther Baker
Es gibt ein leichtes Paket für diesen github.com/ACFBentveld/Await
Wim Pruiksma

Antworten:

161

Eine Alternative zur Antwort von @ tv:

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

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

Beachten Sie, dass .each()selbst synchron ist - die Anweisung, die auf den Aufruf von folgt, .each()wird erst ausgeführt, nachdem der .each()Aufruf abgeschlossen ist. Asynchrone Operationen, die in der .each()Iteration gestartet wurden, werden natürlich auf ihre eigene Weise fortgesetzt. Das ist hier das Problem: Die Aufrufe zum Ausblenden der Elemente sind zeitgesteuerte Animationen, und diese werden in ihrem eigenen Tempo fortgesetzt.

Die obige Lösung verfolgt daher, wie viele Elemente ausgeblendet werden. Jeder Anruf .fadeOut()erhält einen Abschlussrückruf. Wenn der Rückruf feststellt, dass er durch alle beteiligten Originalelemente gezählt wird, können einige nachfolgende Maßnahmen mit der Gewissheit ausgeführt werden, dass das gesamte Ausbleichen abgeschlossen ist.

Dies ist eine vier Jahre alte Antwort (zu diesem Zeitpunkt im Jahr 2014). Ein moderner Weg, dies zu tun, würde wahrscheinlich die Verwendung des Mechanismus "Zurückgestellt / Versprechen" beinhalten, obwohl das oben Genannte einfach ist und gut funktionieren sollte.

Spitze
quelle
4
Ich mag diese Änderung, obwohl ich den Vergleich mit 0 anstelle der logischen Negation (--count == 0) durchführen würde, da Sie herunterzählen. Für mich macht es die Absicht klarer, obwohl es den gleichen Effekt hat.
Tvanfosson
Wie sich herausstellte, war Ihr erster Kommentar zu der Frage genau richtig. Ich habe nach .each () gefragt und dachte, das ist es, was ich wollte, aber wie Sie und Tvanfosson und jetzt Patrick betont haben - es war das letzte FadeOut, an dem ich wirklich interessiert war. Ich denke, wir sind uns alle einig, dass Ihr Beispiel (stattdessen zählen) von Indizes) ist wahrscheinlich die sicherste.
Luther Baker
@ A.DIMO Was meinst du mit "zu kompliziert"? Welcher Teil ist kompliziert?
Pointy
169

Es ist wahrscheinlich zu spät, aber ich denke, dieser Code funktioniert ...

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

Ok, das könnte etwas später sein, aber .promise () sollte auch das erreichen, wonach Sie suchen.

Versprechen Dokumentation

Ein Beispiel aus einem Projekt, an dem ich arbeite:

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

:) :)

nbsp
quelle
7
.promise () ist nicht verfügbar auf .each ()
Michael Fever
25

JavaScript wird synchron ausgeführt, sodass alles, was Sie danach platzieren each(), erst ausgeführt wird, wenn each()es abgeschlossen ist.

Betrachten Sie den folgenden Test:

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

Wenn die Warnung aufgerufen wird, entspricht die Anzahl 1000000, da die Warnung erst ausgeführt wird, wenn sie abgeschlossen each()ist.

user113716
quelle
8
Das Problem im angegebenen Beispiel ist, dass fadeOut in die Animationswarteschlange gestellt wird und nicht synchron ausgeführt wird. Wenn Sie eine verwenden eachund jede Animation separat planen, müssen Sie das Ereignis nach der Animation noch auslösen, nicht nach Abschluss der Animation.
Tvanfosson
3
@tvanfosson - Ja, ich weiß. Je nachdem, was Luther erreichen will, scheint Ihre Lösung (und die von Pointy) der richtige Ansatz zu sein. Aber aus Luthers Kommentaren wie ... Naiv suche ich nach etwas wie elems.each (foo, bar), wobei foo = 'Funktion auf jedes Element angewendet' und bar = 'Rückruf nach Abschluss aller Iterationen'. ... er scheint darauf zu bestehen, dass es ein each()Problem ist. Deshalb habe ich diese Antwort in die Mischung geworfen.
user113716
Du bist richtig Patrick. Ich habe (falsch) verstanden, dass .each () meine Rückrufe plant oder in die Warteschlange stellt. Ich denke, dass das Aufrufen von fadeOut mich indirekt zu dieser Schlussfolgerung geführt hat. tvanfosson und Pointy haben beide Antworten auf das fadeOut-Problem gegeben (was ich wirklich wollte), aber Ihr Beitrag korrigiert tatsächlich mein Verständnis ein wenig. Ihre Antwort beantwortet die ursprüngliche Frage am besten wie angegeben, während Pointy und Tvanfosson die Frage beantworteten, die ich zu stellen versuchte. Ich wünschte, ich könnte zwei Antworten auswählen. Danke, dass du diese Antwort in die Mischung geworfen hast :)
Luther Baker
@ user113716 Ich dachte, dass Funktionen im Inneren eachnicht garantiert vorher aufgerufen wurden alert. Könnten Sie mir die Lesung dazu erklären oder darauf hinweisen?
Maciej Jankowski
5

Ich habe viele Antworten gefunden, die sich mit Arrays befassen, aber nicht mit einem JSON-Objekt. Meine Lösung bestand einfach darin, das Objekt einmal zu durchlaufen, während ein Zähler inkrementiert wurde. Wenn Sie dann das Objekt durchlaufen, um Ihren Code auszuführen, können Sie einen zweiten Zähler inkrementieren. Dann vergleichen Sie einfach die beiden Zähler miteinander und erhalten Ihre Lösung. Ich weiß, dass es etwas klobig ist, aber ich habe bisher keine elegantere Lösung gefunden. Dies ist mein Beispielcode:

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
}

Wie gesagt, es ist nicht das eleganteste, aber es funktioniert und es funktioniert gut und ich habe noch keine bessere Lösung gefunden.

Prost, JP

JimP
quelle
1
Nein, das funktioniert nicht. Es feuert alle .eachs und dann das Letzte ab und alles läuft zur gleichen Zeit. Versuchen Sie, ein setTimeout auf Ihre .eachs zu setzen, und Sie werden sehen, dass es sofort die flag1 === flag2 ausführt
Michael Fever
1

Wenn Sie bereit sind, ein paar Schritte zu machen, könnte dies funktionieren. Es hängt jedoch davon ab, ob die Animationen in der richtigen Reihenfolge beendet werden. Ich denke nicht, dass das ein Problem sein sollte.

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

elems.each( function(i) {
    $(this).fadeOut(200, function() { 
        $(this).remove(); 
        if (i == lastID) {
           doMyThing();
        }
    });
});
Tvanfosson
quelle
Das würde funktionieren - obwohl nicht das, was ich gehofft hatte. Naiv suche ich nach so etwas wie elems.each (foo, bar), wobei foo = 'Funktion auf jedes Element angewendet' und bar = 'Rückruf nach Abschluss aller Iterationen'. Ich werde der Frage etwas mehr Zeit geben, um zu sehen, ob wir auf eine dunkle Ecke von jQuery stoßen :)
Luther Baker
AFAIK - Sie müssen es auslösen, wenn die "letzte" Animation abgeschlossen ist, und da sie asynchron ausgeführt werden, müssen Sie dies im Animationsrückruf tun. Ich mag @ Pointys Version von dem, was ich geschrieben habe, besser, da es nicht von der Bestellung abhängt, nicht dass ich denke, dass das ein Problem wäre.
Tvanfosson
0

wie wäre es mit

$(parentSelect).nextAll().fadeOut(200, function() { 
    $(this).remove(); 
}).one(function(){
    myfunction();
}); 
Mark Schultheiss
quelle
Dies ruft myfunction () definitiv einmal auf (und führt mich in ein anderes jQuery-Konzept ein), aber ich suchte nach einer Garantie dafür, wann es ausgeführt werden würde - nicht nur, dass es einmal ausgeführt werden würde. Und wie Patrick erwähnt, erweist sich dieser Teil als ziemlich einfach. Was ich wirklich gesucht habe, war eine Möglichkeit, etwas aufzurufen, nachdem die letzte fadeOut-Logik ausgeführt wurde, was meiner Meinung nach .one () nicht garantiert.
Luther Baker
Ich denke, dass die Kette das tut - da der erste Teil vor dem letzten Teil läuft.
Mark Schultheiss
0

Sie müssen den Rest Ihrer Anfrage in die Warteschlange stellen, damit sie funktioniert.

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
quelle
0

Ich treffe das gleiche Problem und habe es mit einer Lösung wie dem folgenden Code gelöst:

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

});

Die Lösung löst das folgende Problem: Synchronisieren der in der .each () - Iteration gestarteten asynchronen Operationen mithilfe des Objekts "Zurückgestellt".

Roberto AGOSTINO
quelle
Sie vermissen eine schließende Klammer) vor: drfs.push (internal.promise ()); Zumindest denke ich, es ist vorher ..
Michael Fever
0

Möglicherweise eine späte Antwort, aber es gibt ein Paket, das diese https://github.com/ACFBentveld/Await behandelt

 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
quelle
0

Ich benutze so etwas:

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