Richiamare una funzione jQuery dopo aver completato .each ()


183

In jQuery, è possibile richiamare un callback o attivare un evento dopo l'invocazione .each()(o qualsiasi altro tipo di callback iterativo) è completata .

Ad esempio, vorrei che questa "dissolvenza e rimozione" venisse completata

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

prima di fare alcuni calcoli e inserire nuovi elementi dopo il $(parentSelect). I miei calcoli non sono corretti se gli elementi esistenti sono ancora visibili a jQuery e dormire / ritardare una quantità arbitraria di tempo (200 per ciascun elemento) sembra al massimo una soluzione fragile.

Posso facilmente .bind()la logica necessaria per un callback di un evento, ma non sono sicuro di come richiamare in modo pulito .trigger()dopo che l'iterazione precedente è stata completata . Ovviamente, non posso invocare il trigger all'interno dell'iterazione in quanto si attiva più volte.

Nel caso in cui $.each(), ho considerato di aggiungere qualcosa alla fine dell'argomento dei dati (che avrei cercato manualmente nel corpo dell'iterazione) ma odio essere costretto a farlo, quindi speravo ci fosse qualche altro elegante modo di controllare il flusso rispetto ai callback iterativi.


1
Capisco correttamente che non è tanto lo stesso ".each ()" che vuoi finire, ma piuttosto tutte le animazioni lanciate dalla chiamata ".each ()"?
Punta

Questo è un buon punto. Con questa particolare domanda, sì, sono principalmente preoccupato per il completamento di ".each ()" stesso ... ma penso che tu sollevi un'altra domanda praticabile.
Luther Baker

Esiste un pacchetto leggero per questo github.com/ACFBentveld/Await
Wim Pruiksma,

Risposte:


161

Un'alternativa alla risposta di @ tv:

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

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

Si noti che .each()è di per sé sincrono : l'istruzione che segue la chiamata .each()verrà eseguita solo dopo il .each()completamento della chiamata. Tuttavia, le operazioni asincrone iniziato nel .each()iterazione sarà ovviamente proseguire in modo loro. Questo è il problema qui: le chiamate a sfumare gli elementi sono animazioni basate sul timer e quelle continuano al loro ritmo.

La soluzione sopra, quindi, tiene traccia di quanti elementi vengono sbiaditi. Ogni chiamata a .fadeOut()ottiene un callback di completamento. Quando il callback nota che viene contato attraverso tutti gli elementi originali coinvolti, alcune azioni successive possono essere intraprese con sicurezza che tutto lo sbiadimento è terminato.

Questa è una risposta di quattro anni (a questo punto nel 2014). Un modo moderno per farlo probabilmente implica l'uso del meccanismo Differito / Promessa, sebbene quanto sopra sia semplice e dovrebbe funzionare bene.


4
Mi piace questa modifica, anche se farei il confronto su 0 anziché sulla negazione logica (--count == 0) poiché stai eseguendo il conto alla rovescia. Per me rende più chiaro l'intento, sebbene abbia lo stesso effetto.
tvanfosson,

A quanto pare, il tuo commento iniziale sulla domanda era perfetto. Stavo chiedendo di .each () e ho pensato che fosse quello che volevo, ma come hai sottolineato tu e Tvanfosson e ora Patrick - è stata la dissolvenza finale a cui ero realmente interessato. Penso che siamo tutti d'accordo sul fatto che il tuo esempio (contando invece di indici) è probabilmente il più sicuro.
Luther Baker

@ A.DIMO Cosa intendi con "troppo complicato"? Quale parte è complicata?
Punta l'

169

Probabilmente è tardi ma penso che questo codice funzioni ...

$blocks.each(function(i, elm) {
 $(elm).fadeOut(200, function() {
  $(elm).remove();
 });
}).promise().done( function(){ alert("All was done"); } );

156

Ok, questo potrebbe essere un po 'dopo il fatto, ma .promise () dovrebbe anche ottenere ciò che stai cercando.

Documentazione promettente

Un esempio di un progetto a cui sto lavorando:

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

:)


8
.promise () non è disponibile su .each ()
Michael Fever il

25

JavaScript viene eseguito in modo sincrono, quindi tutto ciò che each()viene inserito non verrà eseguito fino al each()completamento.

Considera il seguente 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);

Quando viene chiamato l'avviso, il conteggio sarà pari a 1000000 perché l'avviso non verrà eseguito fino al each()termine.


8
Il problema nell'esempio fornito è che fadeOut viene inserito nella coda di animazione e non viene eseguito in modo sincrono. Se usi eachun'animazione e pianifichi ciascuna animazione separatamente, devi comunque attivare l'evento dopo l'animazione e non ciascuna termina.
tvanfosson,

3
@tvanfosson - Sì, lo so. Secondo ciò che Lutero vuole realizzare, la tua soluzione (e quella di Pointy) sembra l'approccio giusto. Ma dai commenti di Luther come ... Ingenuamente, sto cercando qualcosa come elems.each (foo, bar) dove foo = 'funzione applicata a ciascun elemento' e bar = 'callback dopo che tutte le iterazioni sono state completate'. ... sembra insistere sul fatto che si tratti di un each()problema. Ecco perché ho gettato questa risposta nel mix.
user113716

Hai ragione Patrick. (Mis) ho capito che .each () stava programmando o mettendo in coda i miei callback. Penso che invocare fadeOut mi abbia indirettamente portato a questa conclusione. tvanfosson e Pointy hanno entrambi fornito risposte per il problema fadeOut (che è quello che stavo davvero cercando), ma il tuo post in realtà corregge un po 'la mia comprensione. La tua risposta in realtà risponde meglio alla domanda originale come affermato mentre Pointy e tvanfosson hanno risposto alla domanda che stavo cercando di porre. Vorrei poter scegliere due risposte. Grazie per 'aver gettato questa risposta nel mix' :)
Luther Baker

@utente113716 Pensavo che le funzioni interne eachnon fossero garantite per essere chiamate prima alert. potresti spiegarmi o indicarmi la lettura su questo?
Maciej Jankowski il

5

Ho trovato molte risposte sulle matrici ma non su un oggetto json. La mia soluzione consisteva semplicemente nell'iterare l'oggetto una volta mentre si incrementava un contatore e quindi quando si scorreva l'oggetto per eseguire il codice è possibile incrementare un secondo contatore. Quindi confronta semplicemente i due contatori e ottieni la tua soluzione. So che è un po 'goffo ma finora non ho trovato una soluzione più elegante. Questo è il mio codice di esempio:

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
}

Come ho detto, non è il più elegante, ma funziona e funziona bene e non ho ancora trovato una soluzione migliore.

Saluti, JP


1
No, questo non funziona. Spara tutte le .each e poi l'ultima cosa e tutto funziona allo stesso tempo. Prova a mettere un setTimeout sul tuo .each e vedrai che esegue subito la flag1 === flag2 immediatamente
Michael Fever

1

Se sei disposto a farlo in un paio di passaggi, questo potrebbe funzionare. Tuttavia dipende dalle animazioni che finiscono in ordine. Non penso che dovrebbe essere un problema.

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

elems.each( function(i) {
    $(this).fadeOut(200, function() { 
        $(this).remove(); 
        if (i == lastID) {
           doMyThing();
        }
    });
});

Funzionerebbe, anche se non quello che speravo. Ingenuamente, sto cercando qualcosa come elems.each (foo, bar) dove foo = 'funzione applicata a ciascun elemento' e bar = 'callback dopo che tutte le iterazioni sono state completate'. Darò alla domanda un po 'più di tempo per vedere se incontriamo qualche angolo oscuro di jQuery :)
Luther Baker

AFAIK: devi attivarlo al termine dell'ultima "animazione" e poiché vengono eseguiti in modo asincrono, dovresti farlo nel callback di animazione. Mi piace la versione di @ Pointy di ciò che ho scritto meglio dal momento che non ha la dipendenza dall'ordinamento, non che penso che sarebbe un problema.
tvanfosson,

0

che dire

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

Questo sicuramente invoca myfunction () una volta (e mi introduce a un altro concetto di jQuery) ma quello che stavo cercando era una garanzia di "quando" avrebbe funzionato, non semplicemente che sarebbe stato eseguito una volta. E, come menziona Patrick, quella parte risulta essere abbastanza facile. Quello che stavo davvero cercando era un modo per invocare qualcosa dopo l'ultima logica di fadeOut che, credo, non garantisce .one ().
Luther Baker

Penso che la catena lo faccia - poiché la prima parte viene eseguita prima dell'ultima parte.
Mark Schultheiss

0

Devi mettere in coda il resto della tua richiesta affinché funzioni.

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

0

Incontro lo stesso problema e ho risolto una soluzione come il seguente codice:

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 soluzione risolve il seguente problema: sincronizzare le operazioni asincrone avviate nell'iterazione .each (), usando l'oggetto Deferred.


Ti manca una parentesi quadra di chiusura) prima: drfs.push (internal.promise ()); almeno penso che sia prima ..
Michael Fever il

0

Forse una risposta tardiva ma esiste un pacchetto per gestire questo 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');
    });

0

Sto usando qualcosa del genere:

$.when(
           $.each(yourArray, function (key, value) {
                // Do Something in loop here
            })
          ).then(function () {
               // After loop ends.
          });
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.