【问题标题】:Callback is being called only after big for loop ends只有在大 for 循环结束后才调用回调
【发布时间】:2017-03-09 17:07:20
【问题描述】:

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

我想说的是相当于以下内容:

如果我在 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

【问题讨论】:

    标签: javascript web browser mqtt paho


    【解决方案1】:

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      这是一个演示问题的代码笔: 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>
      

      【讨论】:

        【解决方案3】:

        Javascript 引擎往往是单线程的。

        因此,如果您处于长时间运行的紧密循环中且不会产生(例如,执行一些 io),那么回调将永远不会有机会运行,直到循环结束

        【讨论】:

          猜你喜欢
          • 2022-12-03
          • 1970-01-01
          • 2018-03-02
          • 1970-01-01
          • 2019-03-25
          • 1970-01-01
          • 2018-09-01
          • 1970-01-01
          • 2015-08-08
          相关资源
          最近更新 更多