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

183

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

Например, я хотел бы, чтобы это "исчезнуть и удалить", чтобы завершить

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

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

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

В случае $.each(), я подумал добавить что-то в конец аргумента данных (что я бы вручную искал в теле итерации), но я бы не хотел, чтобы к этому принудили, поэтому я надеялся, что есть какой-то другой элегантный способ управления потоком относительно итеративных обратных вызовов.

Лютер Бейкер
источник
1
Правильно ли я понимаю, что вы хотите завершить не столько сам ".each ()", сколько анимации, запускаемые вызовом ".each ()"?
Заостренный
Это хороший момент. С этим конкретным вопросом, да, я в основном обеспокоен завершением самой ".each ()" ... но я думаю, что вы поднимаете другой жизнеспособный вопрос.
Лютер Бейкер
Для этого github.com/ACFBentveld/Await
Вим Пруиксма,

Ответы:

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, а теперь и Патрик, указали - это было окончательное увядание, которое меня на самом деле интересовало. Я думаю, что мы все согласны с тем, что ваш пример (считая вместо из индексов), вероятно, самый безопасный.
Лютер Бейкер
@ A.DIMO Что вы подразумеваете под "слишком сложным"? Какая часть сложная?
Заостренный
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() {});
    });

:)

NBSP
источник
8
.promise () недоступно для .each ()
Майкл Лихорадка
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()будет сделано.

user113716
источник
8
Проблема в данном примере заключается в том, что fadeOut помещается в очередь анимации и не выполняется синхронно. Если вы используете eachи планируете каждую анимацию отдельно, вам все равно придется запускать событие после анимации, а не каждую из них завершается.
tvanfosson
3
@tvanfosson - Да, я знаю. В зависимости от того, чего хочет достичь Лютер, ваше решение (и решение Пойнти) кажется правильным. Но из комментариев Лютера вроде ... Наивно я ищу что-то вроде elems.each (foo, bar), где foo = 'функция применяется к каждому элементу' и bar = 'обратный вызов после того, как все итерации завершены'. ... он, кажется, настаивает на том, что это each()проблема. Вот почему я бросил этот ответ в смесь.
user113716
Вы правы, Патрик. Я (неправильно) понял, что .each () планировал или ставил в очередь мои обратные вызовы. Я думаю, что обращение к fadeOut косвенно привело меня к такому выводу. tvanfosson и Pointy предоставили ответы на проблему fadeOut (что я и сделал после), но ваш пост на самом деле немного исправляет мое понимание. Ваш ответ на самом деле лучше всего отвечает на первоначальный вопрос, как указано, в то время как Пойнти и Тванфоссон ответили на вопрос, который я пытался задать. Я хотел бы выбрать два ответа. Спасибо за «добавление этого ответа в смесь» :)
Лютер Бейкер
@ user113716 Я думал, что функции внутри eachне будут вызываться раньше alert. Не могли бы вы объяснить или указать мне на чтение по этому вопросу?
Мацей Янковский
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, и вы увидите, что он сразу запускает 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();
        }
    });
});
tvanfosson
источник
Это сработает - хотя и не то, на что я надеялся. Наивно я ищу что-то вроде elems.each (foo, bar), где foo = 'функция применяется к каждому элементу' и bar = 'обратный вызов после того, как все итерации завершены'. Я дам этому вопросу немного больше времени, чтобы узнать, не встретимся ли мы с темным углом jQuery :)
Лютер Бейкер,
AFAIK - вы должны запустить его, когда завершится «последняя» анимация, и, поскольку они запускаются асинхронно, вам придется делать это в обратном вызове анимации. Мне нравится @ Pointy's версия того, что я написал лучше, так как она не зависит от порядка, не то, чтобы я думал, что это будет проблемой.
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;});
        }
    });
});
Elyx0
источник
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 (), используя отложенный объект.

Роберто АГОСТИНО
источник
Перед вами нет закрывающей скобки): 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.
          });
Softmixt
источник