Chamando uma função jQuery após a conclusão de .each ()

183

Em jQuery, é possível invocar uma chamada de retorno ou desencadear um acontecimento após a invocação de .each()(ou qualquer outro tipo de chamada de retorno iterativo) tenha completado .

Por exemplo, eu gostaria que esse "desbotamento e remoção" fosse concluído

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

antes de fazer alguns cálculos e inserir novos elementos após o $(parentSelect). Meus cálculos estão incorretos se os elementos existentes ainda estiverem visíveis para jQuery e adormecer / atrasar uma quantidade arbitrária de tempo (200 para cada elemento) parecer uma solução quebradiça, na melhor das hipóteses.

Posso facilmente .bind()a lógica necessária para um retorno de chamada do evento, mas não tenho certeza de como limpa invocar o .trigger()após o acima iteração foi concluída . Obviamente, não posso chamar o gatilho dentro da iteração, pois ele seria disparado várias vezes.

No caso de $.each(), considerei adicionar algo ao final do argumento de dados (que eu procuraria manualmente no corpo da iteração), mas eu odiaria ser forçado a isso, então esperava que houvesse alguma outra coisa elegante. maneira de controlar o fluxo com relação aos retornos de chamada iterativos.

Luther Baker
fonte
1
Entendi corretamente que não é tanto o ".each ()" que você deseja concluir, mas todas as animações iniciadas pela chamada ".each ()"?
Pointy
Essa é uma boa opinião. Com esta pergunta em particular, sim, estou principalmente preocupado com a conclusão de ".each ()" em si ... mas acho que você levanta outra questão viável.
Luther Baker
Existe um pacote leve para este github.com/ACFBentveld/Await
Wim Pruiksma

Respostas:

161

Uma alternativa à resposta da @ tv:

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

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

Observe que .each()ele próprio é síncrono - a instrução que segue a chamada para .each()será executada somente após a conclusão da .each()chamada. No entanto, operações assíncronas iniciadas na .each()iteração continuarão, naturalmente, à sua maneira. Essa é a questão aqui: as chamadas para desvanecer os elementos são animações orientadas por cronômetro e continuam no seu próprio ritmo.

A solução acima, portanto, acompanha quantos elementos estão sendo desbotados. Cada chamada para .fadeOut()recebe um retorno de chamada de conclusão. Quando o retorno de chamada percebe que é contado por todos os elementos originais envolvidos, algumas ações subsequentes podem ser tomadas com a certeza de que todo o desbotamento foi concluído.

Esta é uma resposta de quatro anos (neste momento em 2014). Uma maneira moderna de fazer isso provavelmente envolveria o uso do mecanismo Adiado / Promessa, embora o acima seja simples e funcione bem.

Pontudo
fonte
4
Eu gosto dessa alteração, embora eu faça a comparação com 0 em vez da negação lógica (--count == 0), pois você está fazendo a contagem regressiva. Para mim, torna a intenção mais clara, embora tenha o mesmo efeito.
precisa saber é o seguinte
Como se vê, seu comentário inicial sobre a questão foi direto. Eu estava perguntando sobre .each () e pensei que era isso que eu queria, mas como você e tvanfosson e agora patrick apontaram - foi o fadeOut final no qual eu realmente estava interessado. Acho que todos concordamos que seu exemplo (contando de índices) é provavelmente o mais seguro.
Luther Baker
@ A.DIMO O que você quer dizer com "muito complicado"? Que parte é complicada?
Pointy
169

Provavelmente é tarde, mas acho que esse código funciona ...

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

Ok, isso pode ser um pouco depois do fato, mas .promise () também deve conseguir o que você procura.

Documentação da promessa

Um exemplo de um projeto no qual estou trabalhando:

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

:)

nbsp
fonte
8
.promise () não está disponível em .each ()
Michael Fever
25

O JavaScript é executado de forma síncrona; portanto, o que você colocar depois each()não será executado até que each()seja concluído.

Considere o seguinte teste:

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 o alerta é chamado, a contagem será igual a 1000000 porque o alerta não será executado até que each()seja concluído.

user113716
fonte
8
O problema no exemplo dado é que o fadeOut é colocado na fila de animação e não é executado de forma síncrona. Se você usar eache agendar cada animação separadamente, ainda precisará disparar o evento após a animação, e não a cada finalização.
precisa saber é o seguinte
3
@tvanfosson - Sim, eu sei. De acordo com o que Lutero quer realizar, sua solução (e a de Pointy) parece ser a abordagem correta. Mas pelos comentários de Luther, como ... Ingenuamente, estou procurando algo como elems.each (foo, bar) em que foo = 'função aplicada a cada elemento' e bar = 'retorno de chamada após todas as iterações terem sido concluídas'. ... ele parece insistente que é um each()problema. Por isso joguei essa resposta na mistura.
precisa saber é o seguinte
Você está correto, Patrick. Entendi (mis) que .each () estava agendando ou enfileirando meus retornos de chamada. Eu acho que invocar o fadeOut estava indiretamente me levando a essa conclusão. tvanfosson e Pointy forneceram respostas para o problema do fadeOut (que é o que eu realmente estava procurando), mas sua postagem realmente corrige um pouco o meu entendimento. Na verdade, sua resposta responde melhor à pergunta original, enquanto Pointy e tvanfosson responderam à pergunta que eu estava tentando fazer. Eu gostaria de poder escolher duas respostas. Obrigado por 'jogar esta resposta na mistura' :):
Luther Baker
@ user113716 Pensei que as funções internas eachnão tinham garantia de serem chamadas antes alert. você poderia explicar ou me indicar a leitura sobre isso?
Maciej Jankowski
5

Encontrei muitas respostas lidando com matrizes, mas não com um objeto json. Minha solução foi simplesmente iterar o objeto uma vez ao incrementar um contador e, ao iterar o objeto para executar seu código, você pode incrementar um segundo contador. Depois, basta comparar os dois contadores e obter sua solução. Eu sei que é um pouco desajeitado, mas ainda não encontrei uma solução mais elegante. Este é o meu código de exemplo:

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 eu disse, não é o mais elegante, mas funciona e funciona bem e ainda não encontrei uma solução melhor.

Cheers, JP

JimP
fonte
1
Não, isso não funciona. Ele dispara todos os arquivos .each e, em seguida, a coisa final e tudo roda ao mesmo tempo. Tente colocar um setTimeout no seu .each e você verá que ele executa a flag1 === flag2 imediatamente
Michael Fever
1

Se você deseja executar algumas etapas, isso pode funcionar. Depende do término da animação em ordem. Eu não acho que isso deva ser um 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
fonte
Isso funcionaria - embora, não o que eu esperava. Ingenuamente, estou procurando algo como elems.each (foo, bar) em que foo = 'função aplicada a cada elemento' e bar = 'retorno de chamada após todas as iterações terem sido concluídas'. Vou dar a questão um pouco mais de tempo para ver se nos deparamos com algum canto escuro do jQuery :)
Luther Baker
AFAIK - você deve ativá-lo quando a "última" animação for concluída e, como são executadas de forma assíncrona, você deverá fazer isso no retorno de chamada da animação. Gosto da versão do @ Pointy do que escrevi melhor, pois ela não depende da ordem, não que eu pense que isso seria um problema.
precisa saber é o seguinte
0

A respeito

$(parentSelect).nextAll().fadeOut(200, function() { 
    $(this).remove(); 
}).one(function(){
    myfunction();
}); 
Mark Schultheiss
fonte
Definitivamente, invoca myfunction () uma vez (e me apresenta outro conceito do jQuery), mas o que eu procurava era uma garantia de 'quando' seria executado - não apenas que seria executado uma vez. E, como Patrick menciona, essa parte acaba sendo bem fácil. O que eu realmente estava procurando era uma maneira de invocar algo após a última lógica de fadeOut ser executada, o que, eu acho que .one () garante.
Luther Baker
Eu acho que a cadeia faz isso - já que a primeira parte corre antes da última.
Mark Schultheiss
0

Você precisa enfileirar o restante da sua solicitação para que ela 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
fonte
0

Encontro o mesmo problema e resolvi com uma solução como o seguinte 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

});

A solução resolve o seguinte problema: sincronizar as operações assíncronas iniciadas na iteração .each (), usando o objeto Adiado.

Roberto AGOSTINO
fonte
Está faltando um colchete de fechamento) antes: drfs.push (internal.promise ()); pelo menos eu acho que é antes ..
Michael Fever
0

Talvez uma resposta tardia, mas há um pacote para lidar com isso 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
fonte
0

Estou usando algo parecido com isto:

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