我需要遍历一些大型数组并将它们存储在 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,afaik位于 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
});