Подтвердить что ты не робот

Лучший способ перебора массива без блокировки пользовательского интерфейса

Мне нужно перебирать некоторые массивы и хранить их в базовых коллекциях из вызова API. Каков наилучший способ сделать это, не заставляя цикл заставлять интерфейс перестать отвечать на запросы?

Возврат запроса ajax также блокируется, так как возвращаемые данные настолько велики. Я полагаю, что я мог бы разделить его и использовать setTimeout, чтобы он выполнялся асинхронно в небольших кусках, но есть ли более простой способ сделать это.

Я думал, что веб-рабочий будет хорош, но ему нужно изменить некоторые структуры данных, сохраненные в потоке пользовательского интерфейса. Я попытался использовать это для вызова ajax, но когда он возвращает данные в поток пользовательского интерфейса, все еще остается время, когда интерфейс не отвечает.

Заранее спасибо

4b9b3361

Ответ 1

У вас есть выбор с или без WebWorkers:

Без WebWorkers

Для кода, который должен взаимодействовать с DOM или множеством других состояний в вашем приложении, вы не можете использовать webWorker, поэтому обычное решение состоит в том, чтобы разбить вашу работу на куски, выполняющие каждый кусок работы по таймеру. Разрыв между порциями с таймером позволяет ядру браузера обрабатывать другие происходящие события и не только позволяет обрабатывать ввод данных пользователем, но и позволяет рисовать экран.

Обычно вы можете позволить себе обрабатывать более одного на каждый таймер, что более эффективно и быстрее, чем одно на каждый таймер. Этот код дает потоку пользовательского интерфейса возможность обрабатывать любые ожидающие события пользовательского интерфейса между каждым чанком, что будет поддерживать его активным.

function processLargeArray(array) {
    // set this to whatever number of items you can process at once
    var chunk = 100;
    var index = 0;
    function doChunk() {
        var cnt = chunk;
        while (cnt-- && index < array.length) {
            // process array[index] here
            ++index;
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
    }    
    doChunk();    
}

processLargeArray(veryLargeArray);

Вот рабочий пример концепции - не та же функция, а другой длительный процесс, использующий ту же идею setTimeout() для проверки вероятностного сценария с большим количеством итераций: http://jsfiddle.net/jfriend00/9hCVq/


Вы можете превратить вышесказанное в более общую версию, которая вызывает функцию обратного вызова, например, .forEach():

// last two args are optional
function processLargeArrayAsync(array, fn, chunk, context) {
    context = context || window;
    chunk = chunk || 100;
    var index = 0;
    function doChunk() {
        var cnt = chunk;
        while (cnt-- && index < array.length) {
            // callback called with args (value, index, array)
            fn.call(context, array[index], index, array);
            ++index;
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
    }    
    doChunk();    
}

processLargeArrayAsync(veryLargeArray, myCallback, 100);

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

// last two args are optional
function processLargeArrayAsync(array, fn, maxTimePerChunk, context) {
    context = context || window;
    maxTimePerChunk = maxTimePerChunk || 200;
    var index = 0;

    function now() {
        return new Date().getTime();
    }

    function doChunk() {
        var startTime = now();
        while (index < array.length && (now() - startTime) <= maxTimePerChunk) {
            // callback called with args (value, index, array)
            fn.call(context, array[index], index, array);
            ++index;
        }
        if (index < array.length) {
            // set Timeout for async iteration
            setTimeout(doChunk, 1);
        }
    }    
    doChunk();    
}

processLargeArrayAsync(veryLargeArray, myCallback);

с WebWorkers

Если код в вашем цикле не нуждается в доступе к DOM, то можно поместить весь трудоемкий код в webWorker. WebWorker будет работать независимо от основного браузера Javascript, а затем, когда это будет сделано, он может сообщать о любых результатах с помощью postMessage.

WebWorker требует разделения всего кода, который будет выполняться в webWorker, на отдельный файл сценария, но он может выполняться до завершения, не беспокоясь о блокировке обработки других событий в браузере и не беспокоясь о подсказке "unresponsive script" это может произойти при выполнении длительного процесса в основном потоке и без блокировки обработки событий в пользовательском интерфейсе.

Ответ 2

Вот демонстрация этого цикла "async". он "задерживает" итерацию на 1 мс и в течение этой задержки дает UI возможность что-то сделать.

function asyncLoop(arr, callback) {
    (function loop(i) {

        //do stuff here

        if (i < arr.Length) {                      //the condition
            setTimeout(function() {loop(++i)}, 1); //rerun when condition is true
        } else { 
            callback();                            //callback when the loop ends
        }
    }(0));                                         //start with 0
}

asyncLoop(yourArray, function() {
    //do after loop  
})​;

//anything down here runs while the loop runs

Существуют альтернативы, например веб-работники и предлагаемый setImmediate afaik, в IE с префиксом.

Ответ 3

Основываясь на @jfriend00, вот прототип версии:

if (Array.prototype.forEachAsync == null) {
    Array.prototype.forEachAsync = function forEachAsync(fn, thisArg, maxTimePerChunk, callback) {
        let that = this;
        let args = Array.from(arguments);

        let lastArg = args.pop();

        if (lastArg instanceof Function) {
            callback = lastArg;
            lastArg = args.pop();
        } else {
            callback = function() {};
        }
        if (Number(lastArg) === lastArg) {
            maxTimePerChunk = lastArg;
            lastArg = args.pop();
        } else {
            maxTimePerChunk = 200;
        }
        if (args.length === 1) {
            thisArg = lastArg;
        } else {
            thisArg = that
        }

        let index = 0;

        function now() {
            return new Date().getTime();
        }

        function doChunk() {
            let startTime = now();
            while (index < that.length && (now() - startTime) <= maxTimePerChunk) {
                // callback called with args (value, index, array)
                fn.call(thisArg, that[index], index, that);
                ++index;
            }
            if (index < that.length) {
                // set Timeout for async iteration
                setTimeout(doChunk, 1);
            } else {
                callback();
            }
        }

        doChunk();
    }
}