Как я могу вернуть управление (кратковременно) в браузер при интенсивной обработке JavaScript?

Я прочитал сообщение здесь об использовании setTimeout() во время интенсивной обработки DOM (используя JavaScript), но как я могу интегрировать эту функцию с ниже код? Нижеприведенный код отлично подходит для небольшого количества опций, но когда число опций становится слишком большим, мой "ожидающий" анимированный GIF зависает, а локальный JavaScript обрабатывается. Спасибо!

function appendToSelect() {
    $("#mySelect").children().remove() ;
    $("#mySelect").html(
        '<option selected value="' + obj.data[0].value + '">'
        + obj.data[0].name
        + '</option>'
    );
    var j = 1 ;
    for (var i = 1; i < obj.data.length; i++) {
        $("#mySelect").append(
            '<option value="' + obj.data[i].value + '">'
            + obj.data[i].name
            + '</option>'
        ); 
    }
}

Ответ 1

Вот решение:

function appendToSelect() {
  $("#mySelect").children().remove();
  $("#mySelect").html(
    '<option selected value="'+obj.data[0].value+'">'
    + obj.data[0].name
    + '</option>'
  );
  obj.data.splice(0, 1); // we only want remaining data
  var appendOptions = function() {
    var dataChunk = obj.data.splice(0, 10); // configure this last number (the size of the 'chunk') to suit your needs
    for(var i = 0; i < dataChunk.length; i++) {
      $("#mySelect").append(
        '<option value="' + dataChunk[i].value + '">'
        + dataChunk[i].name
        + '</option>'
      );
    }
    if(obj.data.length > 0) {
      setTimeout(appendOptions, 100); // change time to suit needs
    }
  };
  appendOptions(); // kicks it off
}

Не так изящно, как @Borgar's решение, но вы получите эту идею. В принципе, я делаю то же самое, но все в вашей одной функции, а не разбиваю ее на функцию более высокого порядка, как он. Мне нравится его решение, но если вы этого не сделаете, возможно, это сработает для вас.


EDIT: для тех, кто этого не видит сразу, одним из основных различий между этим решением и @Borgar's является то, что это решение позволяет вам установить размер "кусков" данных, которые обрабатываются между каждым таймаутом. @Borgar's после каждого отдельного элемента массива обрабатывается. Если у меня будет время, я попытаюсь создать функцию более высокого порядка, чтобы справиться с этим, чтобы она была более элегантной. Нет promises, хотя!;)


EDIT: Итак, вот моя адаптация @Borgar's, которая позволяет более легко установить размер "chunk" и настроить значение тайм-аута:

function incrementallyProcess(workerCallback, data, chunkSize, timeout, completionCallback) {
  var itemIndex = 0;
  (function() {
    var remainingDataLength = (data.length - itemIndex);
    var currentChunkSize = (remainingDataLength >= chunkSize) ? chunkSize : remainingDataLength;
    if(itemIndex < data.length) {
      while(currentChunkSize--) {
        workerCallback(data[itemIndex++]);
      }
      setTimeout(arguments.callee, timeout);
    } else if(completionCallback) {
      completionCallback();
    }
  })();
}

function appendToSelect() {
  $("#mySelect").children().remove();
  $("#mySelect").html(
    '<option selected value="' + obj.data[0].value + '">'
    + obj.data[0].name
    + '</option>'
  );
  obj.data.splice(0,1); // we only want remaining data      
  incrementallyProcess(function(data) {
    $("#mySelect").append(
    '<option value="' + data.value + '">'
    + data.name
    + '</option>'
   );
  }, obj.data, 10, 100, removeAnimatedGifFunction); // last function not required...
}

Надеюсь, что это поможет - я думаю, что это сочетает в себе лучшее из обоих решений. Уведомление, вторая анонимная функция больше не использует значение индекса, а просто передает весь объект (со значениями и свойствами имени); что делает его немного более чистым, так как индекс текущего объекта действительно не всегда полезен при итерации над вещами, IMO.

Я уверен, что все еще есть вещи, которые можно сделать, чтобы сделать это еще лучше, но это остается для упражнения читателем.;)

Ответ 2

Просто так случилось, что я опубликовал об этом минуту назад здесь. Ниже приведена функция синхронизации по времени:

function processLoop( actionFunc, numTimes, doneFunc ) {
  var i = 0;
  var f = function () {
    if (i < numTimes) {
      actionFunc( i++ );  // closure on i
      setTimeout( f, 10 )
    } 
    else if (doneFunc) { 
      doneFunc();
    }
  };
  f();
}

В вашей ситуации это будет использоваться следующим образом:

function appendToSelect () {

  $("#mySelect").children().remove() ;
  $("#mySelect").html(
      '<option selected value="' + obj.data[0].value + '">'
      + obj.data[0].name
      + '</option>'
  );
  var j = 1 ;

  processLoop(function (i){
    $("#mySelect").append(
        '<option value="' + obj.data[i].value + '">'
        + obj.data[i].name
        + '</option>'
    ); 
  }, obj.data.length);

}

Вы хотите убедиться, что у вас есть закрытие или какой-либо другой доступ к переменной obj в функции итерации.

Надеюсь, что это поможет.

Ответ 3

Если вам нужно что-то более простое, я написал плагин jQuery, чтобы упростить запись асинхронных циклов: jQuery Async.

Используя плагин, ваш код можно переписать как:

function appendToSelect() {
    $("#mySelect").children().remove() ;
    $("#mySelect").html(
        '<option selected value="' + obj.data[0].value + '">'
        + obj.data[0].name
        + '</option>'
    );

    /////////////////////////////
    var i = 1;
    $.whileAsync({
        test: function(){ i < obj.data.length; }
        loop: function()
        {
            $("#mySelect").append(
                '<option value="' + obj.data[i].value + '">'
                + obj.data[i].name
                + '</option>'
            ); 
            i++;
        }
    });
    /////////////////////////////
}

Должна помогать реагированию. Измените параметр "bulk" и "delay" для большего контроля.

Ответ 4

Вам нужно будет переписать функцию для кэширования списка элементов, а затем перебрать список с помощью какого-либо счетчика.

Затем, когда счетчик достигает счетчика% max_num_before_wait == 0, время ожидания вызова возвращается к самой функции.

Обязательно очистите кеш и счетчик в конце полного списка или используйте вторичную функцию с дополнительным параметром count.