我需要遍历一些大型数组并将它们存储在来自 API 调用的主干集合中。在不使循环导致界面无响应的情况下执行此操作的最佳方法是什么?
由于返回的数据如此之大,ajax 请求的返回也会阻塞。我认为我可以将其拆分并使用 setTimeout 使其以较小的块异步运行,但有没有更简单的方法来做到这一点。
我认为网络工作者会很好,但它需要更改保存在 UI 线程上的一些数据结构。我曾尝试使用它来执行 ajax 调用,但是当它向 UI 线程返回数据时,仍有一段时间界面没有响应。
提前致谢
我需要遍历一些大型数组并将它们存储在来自 API 调用的主干集合中。在不使循环导致界面无响应的情况下执行此操作的最佳方法是什么?
由于返回的数据如此之大,ajax 请求的返回也会阻塞。我认为我可以将其拆分并使用 setTimeout 使其以较小的块异步运行,但有没有更简单的方法来做到这一点。
我认为网络工作者会很好,但它需要更改保存在 UI 线程上的一些数据结构。我曾尝试使用它来执行 ajax 调用,但是当它向 UI 线程返回数据时,仍有一段时间界面没有响应。
提前致谢
您可以选择使用或不使用 webWorkers:
对于需要与 DOM 或应用程序中的许多其他状态进行交互的代码,您不能使用 webWorker,因此通常的解决方案是将您的工作分解为多个块,在计时器上完成每个工作块。定时器块之间的中断允许浏览器引擎处理正在发生的其他事件,不仅允许处理用户输入,还允许屏幕绘制。
通常,您可以负担得起在每个计时器上处理多个,这比每个计时器只处理一个更有效和更快。此代码使 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()
想法来测试具有大量迭代的概率场景: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);
与其猜测一次要分多少块,还可以让经过的时间作为每个块的指导,并让它在给定的时间间隔内处理尽可能多的块。无论迭代的 CPU 密集程度如何,这都会在一定程度上自动保证浏览器响应能力。因此,您可以传入一个毫秒值(或仅使用智能默认值),而不是传入块大小:
// 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);
如果循环中的代码不需要访问 DOM,那么可以将所有耗时的代码放入一个 webWorker 中。webWorker 将独立于主浏览器 Javascript 运行,然后当它完成时,它可以用 postMessage 返回任何结果。
一个 webWorker 需要将所有将在 webWorker 中运行的代码分离到一个单独的脚本文件中,但它可以运行到完成而无需担心阻止浏览器中其他事件的处理,也无需担心“无响应脚本”提示当在主线程上执行长时间运行的进程并且不阻塞 UI 中的事件处理时,可能会出现这种情况。
这是执行此“异步”循环的演示。它“延迟”迭代 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
有一些替代方案,如web workers和当前提议的 setImmediate,它在 IE 上,带有前缀。
以@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();
}
}
非常感谢。
我已经更新了代码以添加一些功能。
使用下面的代码,您可以使用数组函数(迭代数组)或映射函数(迭代映射)。
此外,现在有一个用于在块完成时调用的函数的参数(如果您需要更新加载消息会有所帮助),以及在处理循环结束时调用的函数的参数(对于执行下一个操作是必需的)异步操作完成后的步骤)
//Iterate Array Asynchronously
//fn = the function to call while iterating over the array (for loop function call)
//chunkEndFn (optional, use undefined if not using) = the function to call when the chunk ends, used to update a loading message
//endFn (optional, use undefined if not using) = called at the end of the async execution
//last two args are optional
function iterateArrayAsync(array, fn, chunkEndFn, endFn, 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((now() - startTime) > maxTimePerChunk && chunkEndFn !== undefined){
//callback called with args (index, length)
chunkEndFn.call(context,index,array.length);
}
if (index < array.length) {
// set Timeout for async iteration
setTimeout(doChunk, 1);
}
else if(endFn !== undefined){
endFn.call(context);
}
}
doChunk();
}
//Usage
iterateArrayAsync(ourArray,function(value, index, array){
//runs each iteration of the loop
},
function(index,length){
//runs after every chunk completes, this is optional, use undefined if not using this
},
function(){
//runs after completing the loop, this is optional, use undefined if not using this
});
//Iterate Map Asynchronously
//fn = the function to call while iterating over the map (for loop function call)
//chunkEndFn (optional, use undefined if not using) = the function to call when the chunk ends, used to update a loading message
//endFn (optional, use undefined if not using) = called at the end of the async execution
//last two args are optional
function iterateMapAsync(map, fn, chunkEndFn, endFn, maxTimePerChunk, context) {
var array = Array.from(map.keys());
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, key, map)
fn.call(context,map.get(array[index]), array[index], map);
++index;
}
if((now() - startTime) > maxTimePerChunk && chunkEndFn !== undefined){
//callback called with args (index, length)
chunkEndFn.call(context,index,array.length);
}
if (index < array.length) {
// set Timeout for async iteration
setTimeout(doChunk, 1);
}
else if(endFn !== undefined){
endFn.call(context);
}
}
doChunk();
}
//Usage
iterateMapAsync(ourMap,function(value, key, map){
//runs each iteration of the loop
},
function(index,length){
//runs after every chunk completes, this is optional, use undefined if not using this
},
function(){
//runs after completing the loop, this is optional, use undefined if not using this
});