Викликати функцію jQuery після завершення .each ()


183

В JQuery, чи можна викликати зворотний виклик або викликати подія після покликанням до .each()(або будь-якого іншого типу ітераційного зворотного виклику) вже завершено .

Наприклад, я хотів би, щоб це завершилося "в’янення та видалення"

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

перш ніж робити деякі обчислення та вставляти нові елементи після $(parentSelect). Мої підрахунки є невірними, якщо існуючі елементи все ще помітні jQuery і спати / затримувати деяку кількість довільної кількості часу (200 для кожного елемента) в кращому випадку здається крихким рішенням.

Я легко можу .bind()необхідну логіку для зворотного виклику події, але я не впевнений, як правильно викликати .trigger()після того, як зазначена вище ітерація завершена . Очевидно, я не можу викликати спусковий механізм всередині ітерації, оскільки він спрацював би кілька разів.

У випадку $.each(), я розглядав питання про те, щоб додати щось до кінця аргументу даних (що я шукав би вручну в тілі ітерації), але мені б не хотілося змусити до цього, тому я сподівався, що є якась інша елегантна спосіб управління потоком щодо ітеративних зворотних викликів.


1
Чи правильно я розумію, що ви хочете закінчити не стільки «.each ()», скільки швидше всі анімації, запущені викликом «.each ()»?
Pointy

Це хороший момент. З цього конкретного питання, так, я в основному стурбований завершенням самого ".each ()" ... але я думаю, ви ставите ще одне життєздатне питання.
Лютер Бейкер

Є легкий пакет для цього github.com/ACFBentveld/Await
Wim Pruiksma

Відповіді:


161

Альтернатива відповіді @ tv:

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

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

Зауважте, що .each()сама по собі є синхронною - оператор, який слідує за викликом, .each()буде виконаний лише після завершення .each()виклику. Однак асинхронні операції, розпочаті в .each()ітерації, звичайно, продовжуватимуться по-своєму. У цьому і полягає проблема: заклики до згасання елементів - це анімація, керована таймером, і вони продовжуються власним темпом.

Таким чином, рішення, наведене вище, відслідковує, скільки елементів зникає. Кожен дзвінок .fadeOut()отримує зворотний виклик завершення. Коли зворотний виклик помітить, що він зараховується через усі задіяні оригінальні елементи, можна зробити певні дії з упевненістю, що все зникнення закінчилося.

Це відповідь чотирьох років (на даний момент у 2014 році). Сучасний спосіб зробити це, ймовірно, передбачає використання механізму відкладеного / обіцяного, хоча вищезазначене є простим і має працювати чудово.


4
Мені подобається ця зміна, хоча я б зробив порівняння з 0 замість логічного заперечення (--count == 0), оскільки ви рахуєте. Для мене це робить наміри яснішими, хоча він має той же ефект.
tvanfosson

Як виявляється, ваш початковий коментар до цього питання був на місці. Я запитував про .each () і думав, що це те, чого я хотів, але, як ви і tvanfosson, і зараз Патрік вказав - це було остаточним fadeOut, що мене насправді зацікавило. Я думаю, ми всі згодні з вашим прикладом (рахуючи замість цього індексів), мабуть, найбезпечніший.
Лютер Бейкер

@ A.DIMO Що ви маєте на увазі під "занадто складним"? Яка частина складна?
Pointy

169

Можливо, пізно, але я думаю, що цей код працює ...

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

156

Гаразд, це може бути трохи після факту, але .promise () також повинен досягти того, що ви хочете.

Обіцяють документацію

Приклад проекту, над яким я працюю:

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

:)


8
.promise () недоступний .each ()
Майкл Fever

25

JavaScript запускається синхронно, тому те, що ви розміщуєте після each(), не працюватиме, поки не each()буде завершено.

Розглянемо наступний тест:

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

Коли виклик сповіщення, підрахунок дорівнює 1000000, оскільки сповіщення не запускається, поки не each()буде виконано.


8
Проблема в наведеному прикладі полягає в тому, що fadeOut ставиться в чергу анімації і не виконується синхронно. Якщо ви використовуєте eachта плануєте кожну анімацію окремо, вам все одно доведеться запускати подію після анімації, а не кожне завершення.
tvanfosson

3
@tvanfosson - Так, я знаю. Відповідно до того, що Лютер хоче досягти, ваше рішення (і точковий) здається правильним підходом. Але з коментарів Лютера, як ... Наївно, я шукаю щось на зразок elems.each (foo, bar), де foo = 'функція, застосована до кожного елемента', і bar = 'зворотний виклик після завершення всіх ітерацій'. ... він видається наполегливим, що це each()питання. Ось чому я кинув цю відповідь у суміш.
користувач113716

Ти прав Патрік. Я (помилково) зрозумів, що .each () планував або ставив у чергу свої зворотні дзвінки. Я думаю, що посилання на fadeOut опосередковано призвело мене до цього висновку. tvanfosson та Pointy дали відповіді на проблему fadeOut (про що я і справді пішов), але ваш пост насправді трохи виправляє моє розуміння. Ваша відповідь насправді найкраще відповідає на первісне запитання, як було сказано, тоді як Pointy та tvanfosson відповідали на питання, яке я намагався задати. Я хотів би вибрати дві відповіді. Дякую за те, що "кинув цю відповідь у суміш" :)
Лютер Бейкер

@ user113716 Я думав, що функції всередині eachне гарантовано називались раніше alert. ви могли б пояснити чи вказати мені на це читання?
Maciej Jankowski

5

Я знайшов багато відповідей, що стосуються масивів, але не з об'єктом json. Моє рішення полягало в тому, щоб один раз перебрати об'єкт під час збільшення лічильника, а потім, коли ітерація через об'єкт виконує ваш код, ви можете збільшити другий лічильник. Тоді ви просто порівнюєте два лічильники разом і отримуєте рішення. Я знаю, що це трохи незграбно, але поки що я не знайшов більш елегантного рішення. Це мій приклад коду:

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
}

Як я вже говорив, це не найелегантніше, але це працює і працює добре, і я ще не знайшов кращого рішення.

Ура, JP


1
Ні, це не працює. Він обстрілює всі .each, а потім остаточну річ і все працює одночасно. Спробуйте поставити setTimeout на свій .each's, і ви побачите, що він просто запускає flag1 === flag2 одразу
Michael Fever

1

Якщо ви готові зробити це кілька кроків, це може спрацювати. Хоча це залежить від того, анімація закінчується по порядку. Я не думаю, що це має бути проблемою.

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

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

Це спрацювало б - хоча, не те, на що я сподівався. Наївно, я шукаю щось на зразок elems.each (foo, bar), де foo = 'функція, застосована до кожного елемента', і bar = 'зворотний виклик після завершення всіх ітерацій'. Я дам питання ще трохи часу, щоб побачити, чи натрапимо ми на якийсь темний куточок jQuery :)
Лютер Бейкер

AFAIK - вам потрібно запустити його, коли «остання» анімація завершиться, і оскільки вони запускаються асинхронно, вам доведеться це робити в зворотному звороті анімації. Мені подобається версія @ Pointy, що я написав краще, оскільки вона не залежить від замовлення, не те, що я думаю, що це буде проблемою.
tvanfosson

0

а як на рахунок

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

Це, безумовно, викликає функцію myfunction () один раз (і знайомить мене з іншою концепцією jQuery), але те, що я шукав, - це гарантія "коли", що вона буде працювати - не лише те, що вона запуститься один раз. І, як згадує Патрік, ця частина виявляється досить легко. Що я справді шукав, це спосіб викликати щось після останньої логіки fadeOut, яка, я не думаю, що .one () гарантує.
Лютер Бейкер

Я думаю, що ланцюжок робить це - оскільки перша частина працює до останньої частини.
Марк Шультейс

0

Вам потрібно буде поставити в чергу черговий запит, щоб він працював.

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

Я зустрічаюся з тією ж проблемою, і я вирішив таке рішення, як наступний код:

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

});

Рішення вирішує таку проблему: синхронізувати асинхронні операції, розпочаті в ітерації .each (), використовуючи об'єкт Deferred.


Перед вами відсутня дужка закриття: drfs.push (Internal.promise ()); принаймні, я думаю, що це раніше ..
Майкл лихоманка

0

Можливо, пізня відповідь, але є пакет для вирішення цього 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

Я використовую щось подібне:

$.when(
           $.each(yourArray, function (key, value) {
                // Do Something in loop here
            })
          ).then(function () {
               // After loop ends.
          });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.