Wywoływanie funkcji jQuery po zakończeniu funkcji .each ()

183

W jQuery, jest to możliwe do wywołania zwrotnego lub wyzwolić zdarzenie po wystąpieniu pw .each()(lub innego rodzaju iteracyjnej zwrotnego) jest wypełniony .

Na przykład chciałbym, aby to „znikanie i usuwanie” zakończyło się

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

przed wykonaniem obliczeń i wstawieniem nowych elementów po $(parentSelect). Moje obliczenia są niepoprawne, jeśli istniejące elementy są nadal widoczne dla jQuery, a uśpienie / opóźnienie dowolnej ilości czasu (200 dla każdego elementu) wydaje się najlepszym rozwiązaniem kruchym.

Mogę łatwo .bind()niezbędne do zwrotnego logika zdarzeń, ale nie jestem pewien, jak czysto Wywołuje .trigger()po wyżej iteracji została zakończona . Oczywiście nie mogę wywołać wyzwalacza wewnątrz iteracji, ponieważ byłby uruchamiany wiele razy.

W przypadku $.each()rozważałem dodanie czegoś na końcu argumentu danych (którego ręcznie szukałem w treści iteracji), ale nie chciałbym być do tego zmuszony, więc miałem nadzieję, że jest jakiś inny elegancki sposób kontrolowania przepływu w odniesieniu do iteracyjnych wywołań zwrotnych.

Luther Baker
źródło
1
Czy poprawnie rozumiem, że nie tyle sam „.each ()”, który chcesz zakończyć, ale raczej wszystkie animacje uruchomione przez wywołanie „.each ()”?
Pointy
To dobra uwaga. W tym konkretnym pytaniu, tak, martwię się głównie o ukończenie samego „.each ()” ... ale myślę, że podnosicie inne realne pytanie.
Luther Baker
Istnieje lekki pakiet dla tego github.com/ACFBentveld/Await
Wim Pruiksma

Odpowiedzi:

161

Alternatywa dla odpowiedzi @ tv:

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

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

Zauważ, że .each()sama jest synchroniczna - instrukcja następująca po wywołaniu .each()zostanie wykonana dopiero po zakończeniu .each()połączenia. Jednak operacje asynchroniczne rozpoczęte w .each()iteracji będą oczywiście kontynuowane na swój własny sposób. Na tym polega problem: wezwania do zanikania elementów są animacjami sterowanymi zegarem, a te kontynuowane są we własnym tempie.

Dlatego powyższe rozwiązanie śledzi liczbę wyblakłych elementów. Każde połączenie do .fadeOut()otrzymuje oddzwonienie zakończenia. Gdy wywołanie zwrotne zauważy, że jest liczone przez wszystkie pierwotne elementy, można podjąć pewne dalsze działania z pewnością, że wszystkie wygaszanie zostało zakończone.

To czteroletnia odpowiedź (w tym momencie w 2014 r.). Nowoczesny sposób na to prawdopodobnie wymagałby użycia mechanizmu odroczenia / obietnicy, chociaż powyższe jest proste i powinno działać dobrze.

Pointy
źródło
4
Podoba mi się ta zmiana, chociaż porównuję do 0 zamiast logicznej negacji (--count == 0), ponieważ odliczasz czas. Dla mnie cel ten jest wyraźniejszy, chociaż ma ten sam efekt.
tvanfosson
Jak się okazuje, twój początkowy komentarz do pytania był na miejscu. Pytałem o .each () i pomyślałem, że to jest to, czego chciałem, ale jak zauważyliście ty, tvanfosson i teraz patrick - to był ostatni fadeOut, który tak naprawdę mnie interesował. Myślę, że wszyscy zgadzamy się z tym, że twój przykład (licząc zamiast tego indeksów) jest prawdopodobnie najbezpieczniejszy.
Luther Baker
@ A.DIMO Co rozumiesz przez „zbyt skomplikowany”? Jaka część jest skomplikowana?
Pointy,
169

Prawdopodobnie jest za późno, ale myślę, że ten kod działa ...

$blocks.each(function(i, elm) {
 $(elm).fadeOut(200, function() {
  $(elm).remove();
 });
}).promise().done( function(){ alert("All was done"); } );
Sébastien GRAVIER
źródło
156

Ok, może to być trochę po fakcie, ale .promise () powinien również osiągnąć to, czego szukasz.

Dokumentacja obietnicy

Przykład z projektu, nad którym pracuję:

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

:)

nbsp
źródło
8
.promise () nie jest dostępny w .each ()
Michael Fever
25

JavaScript działa synchronicznie, więc cokolwiek umieścisz po nim each(), nie będzie działać, dopóki nie each()zostanie ukończone.

Rozważ następujący 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);

Po wywołaniu alertu liczba wyniesie 1000000, ponieważ alert nie zostanie uruchomiony, dopóki nie each()zostanie zakończony.

użytkownik113716
źródło
8
Problem w podanym przykładzie polega na tym, że fadeOut zostaje umieszczony w kolejce animacji i nie wykonuje się synchronicznie. Jeśli użyjesz eachi zaplanujesz każdą animację osobno, nadal musisz uruchomić zdarzenie po animacji, a nie po każdej z nich.
tvanfosson
3
@tvanfosson - Tak, wiem. Zgodnie z tym, co Luther chce osiągnąć, twoje rozwiązanie (i Pointy'ego) wydaje się właściwym podejściem. Ale na podstawie komentarzy Lutra, takich jak ... Naiwnie szukam czegoś takiego jak elems.each (foo, bar), w którym foo = „funkcja zastosowana do każdego elementu” i bar = „wywołanie zwrotne po zakończeniu wszystkich iteracji”. ... wydaje się nalegać, że to each()problem. Dlatego wrzuciłem tę odpowiedź do miksu.
user113716
Masz rację Patrick. Zrozumiałem (źle), że .each () planuje lub ustawia w kolejce moje wywołania zwrotne. Myślę, że wywołanie fadeOut pośrednio doprowadziło mnie do tego wniosku. Zarówno tvanfosson, jak i Pointy dostarczyli odpowiedzi na problem fadeOut (tego właśnie naprawdę chciałem), ale twój post trochę poprawił moje rozumienie. Twoja odpowiedź najlepiej odpowiada pierwotnemu pytaniu, jak stwierdzono, gdy Pointy i tvanfosson odpowiedzieli na pytanie, które próbowałem zadać. Chciałbym wybrać dwie odpowiedzi. Dzięki za „wrzucenie tej odpowiedzi do miksu” :)
Luther Baker
@ user113716 Myślałem, że funkcje wewnątrz eachnie były wcześniej gwarantowane alert. czy mógłbyś mi to wyjaśnić lub wskazać na lekturę?
Maciej Jankowski
5

Znalazłem wiele odpowiedzi dotyczących tablic, ale nie obiektu Json. Moim rozwiązaniem było po prostu iterowanie obiektu raz podczas zwiększania licznika, a następnie podczas iterowania obiektu w celu wykonania kodu można zwiększyć drugi licznik. Następnie po prostu porównujesz oba liczniki razem i dostajesz swoje rozwiązanie. Wiem, że to trochę niezgrabne, ale jak dotąd nie znalazłem bardziej eleganckiego rozwiązania. To jest mój przykładowy kod:

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
}

Jak powiedziałem, nie jest to najbardziej elegancki, ale działa i działa dobrze, a ja jeszcze nie znalazłem lepszego rozwiązania.

Pozdrawiam, JP

JimP
źródło
1
Nie, to nie działa. Wystrzeliwuje wszystkie .each, a następnie ostatnia rzecz i wszystko działa jednocześnie. Spróbuj umieścić setTimeout na swoim .each, a zobaczysz, że po prostu uruchamia flag1 === flag2 od razu
Michael Fever
1

Jeśli chcesz zrobić to kilka kroków, może to zadziałać. Zależy to jednak od kolejności kończenia animacji. Nie sądzę, że powinien to być problem.

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

elems.each( function(i) {
    $(this).fadeOut(200, function() { 
        $(this).remove(); 
        if (i == lastID) {
           doMyThing();
        }
    });
});
tvanfosson
źródło
To by działało - chociaż nie tak, jak się spodziewałem. Naiwnie szukam czegoś takiego jak elems.each (foo, bar), gdzie foo = „funkcja zastosowana do każdego elementu” i bar = „callback po zakończeniu wszystkich iteracji”. Dam trochę więcej czasu temu pytaniu, czy natrafimy na jakiś ciemny zakątek jQuery :)
Luther Baker
AFAIK - musisz go uruchomić po zakończeniu ostatniej animacji, a ponieważ działają one asynchronicznie, musisz to zrobić w wywołaniu zwrotnym animacji. Podoba mi się wersja @ Pointy tego, co napisałem lepiej, ponieważ nie ma ona zależności od kolejności, nie dlatego, że myślę, że to byłby problem.
tvanfosson
0

co powiesz na

$(parentSelect).nextAll().fadeOut(200, function() { 
    $(this).remove(); 
}).one(function(){
    myfunction();
}); 
Mark Schultheiss
źródło
To zdecydowanie wywołuje funkcję myfunction () raz (i wprowadza mnie do innej koncepcji jQuery), ale to, czego szukałem, było gwarancją „kiedy” zadziała - a nie tylko, że raz. I, jak wspomina Patrick, ta część okazuje się dość łatwa. Tak naprawdę szukałem sposobu na wywołanie czegoś po ostatniej logice fadeOut, która, jak sądzę, nie gwarantuje .one ().
Luther Baker
Myślę, że łańcuch to robi - ponieważ pierwsza część biegnie przed ostatnią.
Mark Schultheiss,
0

Aby kolejność zadziałała, musisz ustawić w kolejce resztę żądania.

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
źródło
0

Napotykam ten sam problem i rozwiązałem go za pomocą następującego rozwiązania:

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

});

Rozwiązanie rozwiązuje następujący problem: zsynchronizować operacje asynchroniczne rozpoczęte w iteracji .each (), używając obiektu Odroczony.

Roberto AGOSTINO
źródło
Brakuje nawiasu zamykającego wcześniej: drfs.push (internal.promise ()); przynajmniej myślę, że to wcześniej ...
Michael Fever
0

Może spóźniona odpowiedź, ale istnieje pakiet do obsługi tego 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
źródło
0

Używam czegoś takiego:

$.when(
           $.each(yourArray, function (key, value) {
                // Do Something in loop here
            })
          ).then(function () {
               // After loop ends.
          });
Softmixt
źródło