其他人已经很好地诊断了问题,浏览器的单线程性质。我将提供一个可能的解决方案:生成器。
这是一个演示问题的代码笔:
http://codepen.io/anon/pen/zZwXem?editors=1111
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 60000;
function log(message) {
const output = document.getElementById('output');
output.value = output.value + '\n' + message;
}
function asyncTask() {
log('Simulated websocket message')
}
function doWork() {
const timer = setInterval(1000, asyncTask);
let total = 0;
for (let i = 1; i < 100000000; i++) {
const foo = Math.log(i) * Math.sin(i);
total += foo;
}
log('The total is: '+ total);
clearInterval(timer);
}
当通过单击“Do Work”按钮调用 doWork() 时,asyncTask 永远不会运行,并且 UI 会锁定。糟糕的用户体验。
以下示例使用生成器来运行长时间运行的任务。
http://codepen.io/anon/pen/jBmoPZ?editors=1111
//Basically disable codepen infinite loop detection, which is faulty for generators
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 120000;
let workTimer;
function log(message) {
const output = document.getElementById('output');
output.value = output.value + '\n' + message;
}
function asyncTask() {
log('Simulated websocket message')
}
let workGenerator = null;
function runWork() {
if (workGenerator === null) {
workGenerator = doWork();
}
const work = workGenerator.next();
if (work.done) {
log('The total is: '+ work.value);
workerGenerator = null;
} else {
workTimer = setTimeout(runWork,0);
}
}
function* doWork() {
const timer = setInterval(asyncTask,1000);
let total = 0;
for (let i = 1; i < 100000000; i++) {
if (i % 100000 === 0) {
yield;
}
if (i % 1000000 == 0) {
log((i / 100000000 * 100).toFixed(1) + '% complete');
}
const foo = Math.log(i) * Math.sin(i);
total += foo;
}
clearInterval(timer);
return total;
}
这里我们在生成器中工作,并创建一个生成器运行器以从 UI 中的“工作”按钮调用。这运行在最新版本的 Chrome 上,我不能代表其他浏览器。通常,您会使用 babel 之类的东西将生成器编译为 ES5 语法以用于生产构建。
生成器每 10000 行计算产生一次,并且每 100000 行发出一次状态更新。生成器运行器“runWork”创建生成器的一个实例并重复调用 next()。然后生成器运行直到它遇到下一个'yield'或return语句。在生成器屈服后,生成器运行器通过在 0 毫秒内调用 setTimeout 并使用自己作为处理函数来放弃 UI 线程。这通常意味着它会在每个动画帧(理想情况下)被调用一次。这一直持续到生成器返回完成标志,此时生成器运行器可以获取返回值并进行清理。
这里是示例的 HTML,以防您需要重新创建 codepen:
<input type='button' value='Do Work' onclick=doWork() />
<textarea id='output' style='width:200px;height:200px'></textarea>