迭代数组而不阻塞UI的最佳方式

我需要迭代一些大型数组,并将它们存储在来自API调用的骨干集合中。 什么是最好的方式做到这一点,而不使循环导致界面变得没有反应?

ajax请求的返回也会因为返回的数据太大而阻塞。 我想我可以分解它,并使用setTimeout使它在较小的块中asynchronous运行,但有一个更简单的方法来做到这一点。

我认为一个networking工作者将是好的,但它需要改变保存在UI线程上的一些数据结构。 我已经尝试使用这个来做ajax调用,但是当它将数据返回到UI线程时,仍然有一段时间,接口没有响应。

提前致谢

您可以select是否使用networking工作者:

没有WebWorkers

对于需要与您的应用程序中的DOM或其​​他状态进行交互的代码,您不能使用webWorker,因此通常的解决scheme是将您的工作分解为大块,然后在定时器上执行每个块。 块与计时器之间的中断允许浏览器引擎处理正在进行的其他事件,并且不仅允许用户input得到处理,而且允许屏幕绘制。

通常情况下,你可以在每个定时器上处理多于一个的定时器,比每个定时器只执行一个定时器更高效和更快速。 此代码为UI线程提供了一个机会来处理每个块之间的任何未完成的UI事件,这将保持UI活跃。

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()来testing一个有很多迭代的概率场景: http : //jsfiddle.net/jfriend00/9hCVq /


你可以把上面的代码变成一个更通用的版本,调用一个像.forEach()这样的callback函数:

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

而不是猜测一次有多less块,也可以让经过的时间成为每个块的指导,并让它在给定的时间间隔内尽可能多地处理。 这可以自动保证浏览器的响应能力,而不pipeCPU的迭代次数是多less。 所以,不是传入一个块大小,而是传递一个毫秒值(或者只是使用一个智能的默认值):

 // 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中运行的所有代码分离成一个单独的脚本文件,但它可以运行到完成,而不用担心阻塞浏览器中其他事件的处理,也不必担心“无响应的脚本”提示可能会在主线程上执行长时间运行的进程时出现。

这里是做这个“asynchronous”循环的演示 。 它“延迟”迭代1ms,在这个延迟之内,它给了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 

还有其他的select,比如networking工作者和当前提出的setImmediate ,它们在IE上有一个前缀。

build立在@ 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(); } }