当javascript在循环中运行时,更新网页以显示进度

Update webpage to show progress while javascript is running in in a loop

本文关键字:网页 显示 更新 javascript 循环 运行时      更新时间:2023-09-26

我已经写了javascript,需要20-30秒来处理,我想通过更新我的网页上的进度条来显示进度。

我使用了setTimeout来尝试允许网页被重新绘制。

我的代码是这样的:

function lengthyFun(...){
   for(...){
      var progress = ...
      document.getElementById('progress-bar').setAttribute('style',"width:{0}%".format(Math.ceil(progress)));
      var x = ...
      // Processing
      setTimeout(function(x) { return function() { ...  }; }(x), 0);
   }
}

它不工作,我知道为什么它不工作,但我不知道如何重构我的代码使它工作

您可能知道,这里的问题是您的主进程(花费大量时间的进程)阻塞了任何呈现。这是因为JavaScript(大部分)是单线程的。

在我看来,你有两种解决办法。第一种方法是将你的主要过程分成不同的部分,并在每个部分之间进行渲染。也就是说,你可以有这样的东西(使用Promises):

var processParts = [/* array of func returning promises */];
function start(){
    // call the first process parts
    var firstPartPromise = (processParts.shift())();
    // chain it with all the other process parts interspersed by updateDisplay
    return processParts.reduce(function(prev, current){
        return val.then(current).then(updateDisplay);
    }, firstPartPromise);
}

您可能需要为承诺(这里一个)填充一个polyfill。如果你使用jQuery,他们有一个(糟糕的非标准)实现。

第二个解决方案是使用webworkers,它允许你在JavaScript中创建线程。它适用于所有现代浏览器。对于您的情况,这可能是最好的解决方案。我从未使用过它们,但你应该能够做这样的事情:

var process = new Worker("process.js");
worker.onmessage(function(event){
    updateProgress(event.data.progress)
});

和in process.js:

postMessage({progress: 0.1});
// stuff
postMessage({progress: 0.4});
// stuff
postMessage({progress: 0.7});
//etc

尝试设置progress元素属性min0, max20000, value0;如果value小于max,则value增加1000;使用持续时间设置为1000setTimeout递归调用函数,直到value到达max

var p = document.querySelector("progress");
function redraw() {
   if (p.value < p.max) {
     p.value += 1000;
     setTimeout("redraw()", 1000)
   }
}
redraw()
<progress max="20000" min="0" value="0"></progress>

我知道有几种方法可以通过Javascript触发顺序的HTML重绘:

  • 增量超时时间
  • 递归方法调用
第一种也是最简单的方法是在循环的超时间隔上使用一个乘数(比如迭代器)。如果操作独立于外部变量,并且只需要运行有限且相对较少的次数,则这种方法应该足够了。需要/可能发生的操作越多,对资源的压力就越大——仅仅是计算间隔。当处理时间超过超时间隔时,另一个缺点就会出现,导致对观察到的重绘间隔产生冲击。这样做的结果可能是网页完全冻结,直到所有操作完成。

例子
for (var i=0, limit=n; i<limit; i++) {
    setTimeout((function(params) {
        return  function() {
            some_func(params);
        }
    })(param_values), i*1000);
}

第二种方法稍微复杂一些,但无论超时时间如何,都保证每次操作之间的重绘。这里,超时只影响重绘和抵制连续操作变量的影响之间的时间。然而,当前操作的处理时间仍然是观察间隔的一个因素,并且如果操作是计算密集型的,则在重绘之间仍然会冻结网页。

例子
var limit = n;
var i = 0;
recursive_timeout();
function recursive_timeout() {
    setTimeout((function(params) {
        return function() {
            some_func(params);
            i++;
            if (i<limit) {
                recursive_timeout();
            }
        }
    })(param_values, i, limit), 1000);
}

精炼示例(基于guest271314的回答)

var still_true = true;
recursive_timeout();
function recursive_timeout() {
    some_func(params);
    if (still_true) {
        setTimeout(function() {recursive_timeout();}, 1000);
    }
}

虽然增量方法对于简单任务很好,但递归将可靠地执行重绘制。如果每次操作的处理时间长是一个问题,那么除了使用递归之外,可能值得深入研究异步任务,以避免导致网页暂时不可用。

无论如何,希望这有助于!

哈!刚刚意识到guest271314提出了一个更优雅的递归方法的例子…好吧,多给点信息也无妨。