【问题标题】:Optimal solution to creating a "run loop" in JavaScript在 JavaScript 中创建“运行循环”的最佳解决方案
【发布时间】:2018-07-20 07:52:09
【问题描述】:

结果是the while loop blocks the event loop in JavaScript,如果您希望您的应用程序响应外部/异步事件,这会阻止它成为创建运行循环的可行解决方案。

我的问题是,使用什么技术可以使“运行循环”无限/恒定但仍然具有高性能并且不会阻塞事件循环。

我运行了一个快速测试来查看whilesetTimeout(fn, 0)(浏览器)和setImmediate(fn)(node.js)的性能。

var x = 1000
var isNode = typeof window == 'undefined'

function a() {
  var start = (new Date()).getTime()
  var q = 0

  function next() {
    q++
    if (q < x) {
      if (isNode) {
        setImmediate(next)
      } else {
        setTimeout(next, 0)
      }
    } else {
      var end = (new Date).getTime()
      console.log('a', end - start)
    }
  }

  next()
}

function b() {
  var start = (new Date()).getTime()
  var q = 0

  while (q < x) {
    q++
  }

  var end = (new Date).getTime()
  console.log('b', end - start)
}

a()
b()

node.js:

a = 20
b = 0

浏览器:

a = 5039
b = 0

有没有一种方法可以像在 JavaScript 中执行 while 循环一样高效地执行“无限 while 循环”。 setImmediatesetTimeout 快得多,但仍然比基本的 while 循环慢很多。

【问题讨论】:

  • 为什么你需要一个无限循环?浏览器事件循环的重点就是这样做。您想处理哪些事件循环无法处理的外部/异步事件? (真诚地问)
  • 事件循环应用程序运行循环。除非你想要一些恒定节奏的间隔,例如用于渲染循环或物理引擎。
  • @LancePollard 只需将虚拟机放在自己的线程中(客户端的 webworker,节点上的子进程)。
  • 基于浏览器的 JS,我认为 JS 通常被优化以响应事件,而不是长时间只做一件事。事件循环是监视事件的无限循环。因此,如果您有一个循环正在运行,那么就没有时间响应事件。
  • @Nishant 正确,这是我发布的第一个链接。问题是解决方法是什么。

标签: javascript event-loop


【解决方案1】:

您可以使用 async/await 在 while 循环中让步:

// This can only be logged when the event loop
// is yielded to in the infinite loop below.
// So, it can only ever be logged between `#1` and `#2` being logged;
// it can sometimes occur more than once, or not at all between `#1` and `#2`
const interval = setInterval( _ => console.log( '#1.5' ), 0 );

(async _ => {

  // Using a for loop so this demo doesn't run forever.
  // Can use an infinite loop instead
  for ( let i = 0; i < 150; i++ ) { // while( true ) {

    console.log( '#1 About to yield, ' );
    await new Promise( resolve => setTimeout( _ => resolve(), 0 ) ); // yield
    console.log( '#2 Done yielding (other callbacks may have run)' );

  }

  clearInterval( interval );

})();

在 node.js 中使用 setImmediate 而不是 setTimeout。不要使用process.nextTick

使用计时器进行演示:

var start = (new Date()).getTime()
var q = 0, x = 1000;

;(async _ => {
  while (q < x) {
    await new Promise( resolve => setTimeout( _ => resolve(), 0 ) );
    q++
  }

  var end = (new Date).getTime()
  console.log('a', end - start)
})();

【讨论】:

  • 我在测试中尝试了setImmediatesetTimeout,但它们对性能的影响太大而无法实现。
  • @LancePollard 为了什么可行?事件循环轮询需要时间,并且在事件循环中运行所有消息需要不确定的时间量。你无法解决这个问题。您将数字递增 1000 次 (0ms) 的时间与轮询事件循环 1000 次并运行任何可能在事件循环中排队的任意代码的时间进行比较。
  • @Paulpro 演示是错误的,console.log 的时间不会等到while 循环结束......这就是你得到 0ms 的原因
  • @MarcosCasagrande 你说得对,谢谢!如果您不介意编辑,可以将这两行移到异步箭头函数中;否则我下次在我的电脑前会这样做
【解决方案2】:

这样的事情会起作用:

var THRESHOLDCOUNT = 10000
var THRESHOLDTIME = 15

function loop(fn) {
  var start = Date.now()
  var changed = false
  var x = 0

  while (!changed) {
    while (x < THRESHOLDCOUNT) {
      x++
      fn()
    }

    var end = Date.now()
    changed = end - start > THRESHOLDTIME
  }

  // make room for external events.
  setImmediate(loop)
}

【讨论】:

    【解决方案3】:

    如果您真的需要一个高性能循环,我会说使用requestAnimationFrame(),它经过高度优化且非常易于使用。

    https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

    【讨论】:

    • 这是不正确的。使用requestAnimationFrame 运行测试会导致最慢的c = 16637ms
    • requestAnimationFrame 用于渲染循环,而不是用于高性能的东西。
    猜你喜欢
    • 2019-01-27
    • 1970-01-01
    • 2012-03-22
    • 1970-01-01
    • 2023-01-02
    • 1970-01-01
    • 2020-04-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多