Javascript:避免急切的异步执行并优先调用事件循环

Javascript: avoid eager async execution and prioritize calls on event loop

我的 Javascript 应用程序需要在 UI 线程上做一些繁重的计算(让我们忽略这个问题范围内的网络工作者)。
幸运的是,要完成的工作是令人尴尬的并行,可以切割成数千个小的异步函数。

我想做的是:在事件循环中将所有这些异步函数排队,但优先处理 UI 个事件。
如果这有效,对 UI 响应能力的影响应该非常低。每个功能只需要几毫秒。

问题是 Javascript 急切地执行异步代码。直到你 运行 进入 await IO 之类的东西,它才会像同步一样执行所有代码。

我想分解一下:当执行遇到异步调用时,应该放在事件循环中,但在执行之前,应该检查是否有UI事件等待处理.
基本上,我正在寻找一种方法来立即从我的辅助函数中 yield,而不返回结果。

我已经试验并找到了解决方法。与其直接调用异步函数,不如将其包装在超时中。这几乎正​​是我想要的:异步调用未执行但在事件循环中排队。
这是一个基于反应的简单示例。单击后,按钮将立即更新,而不是等待所有这些计算完成。
(该示例可能看起来像可怕的代码,我在渲染函数中进行了大量计算。但这只是为了保持简短。异步函数从哪里开始并不重要,它们总是阻塞单个 UI线程):

// timed it, on my system it takes a few milliseconds
async function expensiveCalc(n: number) {
  for (let i = 0; i < 100000; i++) {
    let a = i * i;
  }
  console.log(n);
}

const App: React.FC = () => {
  const [id, setId] = useState("click me");

  // many inexpensive calls that add up
  for (let i = 0; i < 1000; i++) {
    setTimeout(() => expensiveCalc(i), 1);
  }

  // this needs to be pushed out as fast as possible
  return (
    <IonApp>
      <IonButton
        onClick={() => setId(v4())}>
        {id}
      </IonButton>
    </IonApp>
  );
};

现在,这看起来很整洁。渲染函数的同步执行永远不会中断。所有的计算函数都在渲染完成后排队完成,甚至不必异步。

我不明白的是:即使您多次单击按钮,它也能正常工作! 我的期望是事件循环中的所有调用都被平等对待。所以第一次render应该是instant,第二次需要等待所有超时先执行。
但是我看到的是:

所以事件循环没有按顺序执行。不知何故 UI 事件被优先考虑。棒极了。但是它是如何工作的呢?我可以自己确定事件的优先级吗?

Here's a sandbox where you can play around with the example.

So the event loop does not execute in-order. And somehow UI events are prioritized. That's awesome. But how does it work?

浏览器事件循环是一个复杂的野兽。它确实包含多个 "task queues" for different "task sources",浏览器可以自由优化它们之间的调度。但是,每个队列本身确实遵循该顺序。

And can I prioritize events myself?

不 - 不是没有编写自己的调度程序和 运行 在事件循环中。

对发生的事情有很好的解释(浏览器的事件循环中有任务优先级)。

请注意,我们可能在不久的将来1 有一个 API 作为网络开发者来处理这个问题.

但我必须指出,您最初愿意做的事情似乎正是 requestIdleCallback 设计的目的:仅当事件循环中没有其他事情发生时才执行回调。

btn.onclick = (e) => {
  for (let i = 0; i<100000; i++) {
    requestIdleCallback( () => {
      log.textContent = 'executed #' + i;
    });
  }
  console.log( 'new batch' );
}
<pre id="log"></pre>
<button id="btn">click me</button>

这样你就不会依赖于取决于实现的未定义行为,此处规范要求该行为。



1.这个 API 已经可以在 Chrome 中通过打开 chrome://flags/#enable-experimental-web-platform-features[ 进行测试=38=]