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,第二次需要等待所有超时先执行。
但是我看到的是:
- 点击按钮 -> 文本已更新
- 立即再次点击按钮 -> 文本已更新
- 查看控制台日志:它计数到 1000,然后从 0 开始重新计数到 1000。
所以事件循环没有按顺序执行。不知何故 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=]
我的 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,第二次需要等待所有超时先执行。
但是我看到的是:
- 点击按钮 -> 文本已更新
- 立即再次点击按钮 -> 文本已更新
- 查看控制台日志:它计数到 1000,然后从 0 开始重新计数到 1000。
所以事件循环没有按顺序执行。不知何故 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=]