【问题标题】:Can promises be used to handle really long and blocking loops in JavaScript?可以使用 Promise 来处理 JavaScript 中非常长的阻塞循环吗?
【发布时间】:2020-02-04 07:56:19
【问题描述】:

假设我有以下for 循环,这显然会阻塞事件循环一段时间:

function somethingExpensive() {
  let i = 0;
  while (true) {
    // Something expensive...
    if (++i === 1e10) return 'Some value.'
  }
}

我可以将该操作(for 循环)包装在 Promise 中,这样它就不会阻塞“主线程”吗?

类似的东西:

function somethingExpensive() {
  return new Promise((resolve) => {
    let i = 0;
    while (true) {
      // Something expensive...
      if (++i === 1e10) resolve('Some value.');
    }
  });
}

【问题讨论】:

  • Javascript 在设计上是单线程的。 Promise promise 将来会在某个时候执行,但它们仍然会阻塞主线程。不过,您可以解决这个问题。在节点中,使用multi-threading。在网络上,使用service workers
  • 不,因为一旦 Promise 执行器中的代码启动,它将一直持续到它结束 - 并且 Promise 执行器无论如何都是同步执行的,因此该解决方案中根本没有异步
  • 除非你使用异步工作者,否则任何需要很长时间的东西都会阻塞事件循环。主事件循环将所有事件处理程序排入队列,因此您不应运行任何需要超过几毫秒才能完成的操作,否则您的页面会感觉迟滞或无响应。

标签: javascript loops promise


【解决方案1】:

仅在有限的范围内。如果昂贵的操作发生在同一个Javascript环境中,环境的资源将被昂贵的操作占用,无论昂贵的操作是同步发生还是异步发生(如Promise.resolve之后)。例如,用户可能无法在操作进行时与页面交互。

改为使用服务工作者,以便在完全独立的环境中进行昂贵的操作,从而可以正常与原始页面进行交互。例如:

const workerFn = () => {
  // something expensive
  while (true) {
  }
};
const workerFnStr = `(${workerFn})();`;
const blob = new Blob([workerFnStr], { type: 'text/javascript' });
const worker = new Worker(window.URL.createObjectURL(blob));

button.onclick = () => console.log('clicked');
body {
  height: 1000px;
}
<button id="button">click</button>

那里的工作人员会消耗大量资源,但原始窗口将保持可滚动和可交互的状态,如所愿。如果没有工作人员,您要么不得不忍受原始窗口在进行昂贵的操作时没有响应,要么将昂贵的操作错开为多个便宜的块,例如每 100 毫秒调用一次。 (虽然,即使使用这种方法,窗口也可能不会像人们希望的那样响应 - 使用工作人员更好)

【讨论】:

    【解决方案2】:

    不,您不能单独使用 Promise 来允许所有其他事件在事件循环中运行。这是因为 ES6+ 中的原生 Promise 是高优先级事件,并且会在其他类型的事件之前轮到它们,所以其他类型的事件仍然会被饿死。

    允许所有其他事件与循环交错的常用方法是在某个时间点使用简短的setTimeout() 来控制循环的迭代,以便在循环的迭代之间存在合理的延迟。至少有短暂延迟的setTimeout() 允许所有其他类型的事件轮流。

    使用常规for 循环执行此操作的一种方法是这样的(它使用承诺只是在内部暂停for 循环,但真正的事件循环帮助来自delay() 中的setTimeout()功能):

    function delay(t) {
        return new Promise((resolve) => {
            setTimeout(resolve, t);
        });
    }
    
    
    async function doStuff() {
      let max = 100_000_000_000_000n;
    
        let cntr = 1;
        for (let i = 1n; i <= max; i = i + 1n) {
            // let other stuff run once in every 100 iterations of the loop
            if (cntr % 100 === 0) {
                // give other things in the event queue a chance to run 
                // note: this use setTimeout(), not just promises
                await delay(10);  
            }
            // do the work for your loop here
        }
    }    
    

    如果您真的想将耗时的循环与事件循环处理分开,那么最好将其完全从同一个事件循环中取出。在 node.js 中你可以使用 Worker 线程,在浏览器中你可以使用 WebWorkers。然后它们可以通过消息传递将结果返回给主线程。

    【讨论】:

      猜你喜欢
      • 2017-11-08
      • 2022-08-20
      • 1970-01-01
      • 2018-03-27
      • 1970-01-01
      • 2020-09-01
      • 1970-01-01
      • 2018-06-11
      • 1970-01-01
      相关资源
      最近更新 更多