只有在大循环结束后才会调用回调

时间:2017-03-09 17:07:20

标签: javascript web browser mqtt paho

我通过websockets(paho-mqtt)在浏览器中接收数据,但问题是接收回调只在另一个任务结束时被激活(大循环)并且它被所有堆叠数据触发,我不是丢失数据只是迟到了。即使有循环运行,回调是否应该被触发?这里发生了什么?否则,我怎样才能实现这一点,在循环内继续接收?

我想说的是以下内容:

如果我在chrome中执行此操作

setTimeout(() => {
  console.log('hello!');
}, 10);
for (var i = 0; i < 50000; i++) {
  console.log('for array');
}

我得到了

50000 VM15292:5 for array
VM15292:2 hello!

我不应该得到这样的东西吗?

1000 VM15292:5 for array
VM15292:2 hello!
49000 VM15292:5 for array

3 个答案:

答案 0 :(得分:3)

当您在浏览器中运行JavaScript代码时(除非使用Web Workers或其他特殊技术),它将在单个线程上执行。这可能听起来不太重要,但确实如此。

您的代码包含for-loop(同步)和对setTimeout的调用(asychronous)。由于只能同时运行一段JavaScript,因此{for循环永远不会被setTimeout中断。

事实上,如果您的for循环包含需要超过10毫秒的极其密集的操作,那么您的setTimeout回调实际上可能延迟超过该标记,因为浏览器始终在继续运行事件循环之前等待当前正在执行的代码完成。

setTimeout(() => {
  console.log('hello!');
}, 10);
for (var i = 0; i < /* 50000 */ 5; i++) {
  console.log('for array');
}

答案 1 :(得分:0)

Javascript引擎往往是单线程的。

因此,如果你处于一个长时间运行的紧凑循环中(例如做一些io),那么回调将永远不会有机会运行直到循环结束

答案 2 :(得分:0)

其他人已经很好地诊断出问题,浏览器的单线程性质。我将提供一个可能的解决方案:发电机。

这是一个演示问题的codepen: 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);
}

通过点击“工作”来调用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;
}

在这里,我们在生成器中工作,并创建一个生成器运行器,以便从“工作”中调用。用户界面中的按钮。这是在最新版本的Chrome上运行的,我无法代替其他浏览器。通常,您可以使用像babel这样的东西将生成器编译为生成版本的ES5语法。

生成器每10000行计算一次,每100000行发出一次状态更新。发电机转轮&#39; runWork&#39;创建生成器的实例并重复调用next()。然后发电机运行直到它达到下一个产量。或退回声明。在生成器生成之后,生成器运行器然后通过调用setTimeout(0毫秒)并将其自身用作处理函数来放弃UI线程。这通常意味着它将在每个动画帧(理想情况下)被调用一次。直到发电机返回完成标志为止,此时发电机转轮可以获得返回值并清理。

这里是示例的HTML,以便您需要重新创建codepen:

<input type='button' value='Do Work' onclick=doWork() />
<textarea id='output' style='width:200px;height:200px'></textarea>