Invocar una función jQuery después de que .each () se haya completado

183

En jQuery, es posible invocar una devolución de llamada o activar un evento después de una invocación de .each()(o cualquier otro tipo de devolución de llamada iterativo) ha completado .

Por ejemplo, me gustaría completar este "fundido y eliminación"

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

antes de hacer algunos cálculos e insertar nuevos elementos después de $(parentSelect). Mis cálculos son incorrectos si los elementos existentes aún son visibles para jQuery y dormir / retrasar una cantidad arbitraria de tiempo (200 para cada elemento) parece, en el mejor de los casos, una solución frágil.

Puedo fácilmente .bind()la lógica necesaria para una devolución de llamada de evento, pero no estoy seguro de cómo invocar limpiamente .trigger()después de que se haya completado la iteración anterior . Obviamente, no puedo invocar el disparador dentro de la iteración, ya que se dispararía varias veces.

En el caso de $.each(), he considerado agregar algo al final del argumento de datos (que buscaría manualmente en el cuerpo de la iteración) pero odiaría que me obligaran a hacerlo, así que esperaba que hubiera algún otro elegante forma de controlar el flujo con respecto a las devoluciones de llamada iterativas.

Luther Baker
fuente
1
¿Entiendo correctamente que no es tanto el ".each ()" en sí mismo lo que quieres terminar, sino todas las animaciones lanzadas por la llamada ".each ()"?
Pointy
Ese es un buen punto. Con esta pregunta en particular, sí, me preocupa principalmente la finalización de ".each ()" en sí ... pero creo que plantea otra pregunta viable.
Luther Baker,
Hay un paquete liviano para este github.com/ACFBentveld/Await
Wim Pruiksma

Respuestas:

161

Una alternativa a la respuesta de @ tv:

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

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

Tenga en cuenta que .each()sí mismo es sincrónico : la instrucción que sigue a la llamada a .each()se ejecutará solo después de que .each()se complete la llamada. Sin embargo, las operaciones asincrónicas iniciadas en la .each()iteración, por supuesto, continuarán a su manera. Ese es el problema aquí: las llamadas para desvanecer los elementos son animaciones controladas por temporizador, y continúan a su propio ritmo.

La solución anterior, por lo tanto, realiza un seguimiento de cuántos elementos se desvanecen. Cada llamada a .fadeOut()recibe una devolución de llamada de finalización. Cuando la devolución de llamada se da cuenta de que se cuenta a través de todos los elementos originales involucrados, se pueden tomar medidas posteriores con la confianza de que todo el desvanecimiento ha finalizado.

Esta es una respuesta de cuatro años (en este momento en 2014). Una forma moderna de hacer esto probablemente implicaría usar el mecanismo de Diferido / Promesa, aunque lo anterior es simple y debería funcionar bien.

Puntiagudo
fuente
44
Me gusta este cambio, aunque haría la comparación a 0 en lugar de la negación lógica (--count == 0) ya que estás haciendo la cuenta regresiva. Para mí, aclara la intención, aunque tiene el mismo efecto.
tvanfosson
Como resultado, su comentario inicial sobre la pregunta fue acertado. Estaba preguntando sobre .each () y pensé que eso era lo que quería, pero como tú y tvanfosson y ahora Patrick han señalado, fue el desvanecimiento final en el que realmente estaba interesado. Creo que todos estamos de acuerdo en que tu ejemplo (contando en su lugar de índices) es probablemente el más seguro.
Luther Baker,
@ A.DIMO ¿Qué quiere decir con "demasiado complicado"? ¿Qué parte es complicada?
Puntiagudo
169

Probablemente sea demasiado tarde, pero creo que este código funciona ...

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

Ok, esto podría ser un poco después del hecho, pero .promise () también debería lograr lo que buscas.

Documentación de la promesa

Un ejemplo de un proyecto en el que estoy trabajando:

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

:)

nbsp
fuente
8
.promise () no está disponible en .each ()
Michael Fever
25

JavaScript se ejecuta sincrónicamente, por lo que lo que coloque después each()no se ejecutará hasta que each()se complete.

Considere la siguiente prueba:

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

Cuando se llama a la alerta, el recuento será igual a 1000000 porque la alerta no se ejecutará hasta each()que finalice.

usuario113716
fuente
8
El problema en el ejemplo dado es que el fadeOut se coloca en la cola de animación y no se ejecuta sincrónicamente. Si usa un eachy programa cada animación por separado, aún tiene que disparar el evento después de la animación, no cada uno termina.
tvanfosson
3
@tvanfosson - Sí, lo sé. Según lo que Luther quiere lograr, su solución (y la de Pointy) parece ser el enfoque correcto. Pero de los comentarios de Luther como ... Ingenuamente, estoy buscando algo como elems.each (foo, bar) donde foo = 'función aplicada a cada elemento' y bar = 'devolución de llamada después de que se hayan completado todas las iteraciones'. ... parece insistente en que es un each()problema. Es por eso que arrojé esta respuesta a la mezcla.
user113716
Tienes razón Patrick. Entendí (mal) que .each () estaba programando o poniendo en cola mis devoluciones de llamada. Creo que invocar a fadeOut me estaba llevando indirectamente a esa conclusión. tvanfosson y Pointy han proporcionado respuestas para el problema de fadeOut (que es lo que realmente buscaba), pero su publicación en realidad corrige un poco mi comprensión. Su respuesta en realidad responde mejor a la pregunta original como se indicó mientras Pointy y tvanfosson respondieron la pregunta que estaba tratando de hacer. Desearía poder elegir dos respuestas. Gracias por 'poner esta respuesta en la mezcla' :)
Luther Baker
@ user113716 Pensé que las funciones internas eachno se garantizaban antes alert. ¿Podría explicarme o señalarme la lectura de esto?
Maciej Jankowski
5

Encontré muchas respuestas relacionadas con matrices pero no con un objeto json. Mi solución fue simplemente iterar a través del objeto una vez mientras incrementaba un contador y luego, al iterar a través del objeto para ejecutar su código, puede incrementar un segundo contador. Luego, simplemente compara los dos contadores y obtén tu solución. Sé que es un poco torpe, pero hasta ahora no he encontrado una solución más elegante. Este es mi código de ejemplo:

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
}

Como dije, no es el más elegante, pero funciona y funciona bien y todavía no he encontrado una solución mejor.

Saludos, JP

Pobre
fuente
1
No, esto no funciona. Se dispara todo. Cada uno y luego lo último y todo funciona al mismo tiempo. Intenta poner un setTimeout en tu .each y verás que solo ejecuta flag1 === flag2 de inmediato
Michael Fever
1

Si está dispuesto a dar un par de pasos, esto podría funcionar. Sin embargo, depende de que las animaciones terminen en orden. No creo que eso deba ser 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();
        }
    });
});
tvanfosson
fuente
Esto funcionaría, aunque no es lo que esperaba. Ingenuamente, estoy buscando algo como elems.each (foo, bar) donde foo = 'función aplicada a cada elemento' y bar = 'devolución de llamada después de que se hayan completado todas las iteraciones'. Le daré a la pregunta un poco más de tiempo para ver si nos encontramos con algún rincón oscuro de jQuery :)
Luther Baker
AFAIK: debe activarlo cuando se complete la "última" animación y, dado que se ejecutan de forma asincrónica, deberá hacerlo en la devolución de llamada de la animación. Me gusta más la versión de @ Pointy de lo que escribí, ya que no depende de los pedidos, no creo que sea un problema.
tvanfosson
0

qué pasa

$(parentSelect).nextAll().fadeOut(200, function() { 
    $(this).remove(); 
}).one(function(){
    myfunction();
}); 
Mark Schultheiss
fuente
Esto definitivamente invoca myfunction () una vez (y me presenta otro concepto de jQuery), pero lo que estaba buscando era una garantía de "cuándo" se ejecutaría, no simplemente de que se ejecutaría una vez. Y, como Patrick menciona, esa parte resulta ser bastante fácil. Lo que realmente estaba buscando era una forma de invocar algo después de que se ejecutó la última lógica de fadeOut que, no creo que .one () garantice.
Luther Baker,
Creo que la cadena hace eso, ya que la primera parte se ejecuta antes que la última parte.
Mark Schultheiss
0

Debe poner en cola el resto de su solicitud para que funcione.

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

Me encuentro con el mismo problema y lo resolví con una solución como el siguiente código:

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 solución resuelve el siguiente problema: sincronizar las operaciones asincrónicas iniciadas en la iteración .each (), utilizando el objeto diferido.

Roberto AGOSTINO
fuente
Te falta un corchete de cierre) antes: drfs.push (internal.promise ()); al menos creo que es antes ..
Michael Fever
0

Tal vez una respuesta tardía, pero hay un paquete para manejar esto 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
fuente
0

Estoy usando algo como esto:

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