gpt4 book ai didi

javascript - 为什么嵌套的 postMessage 会导致 rAF 在 setTimeout 之前触发?

转载 作者:行者123 更新时间:2023-11-28 00:32:52 29 4
gpt4 key购买 nike

代码:

var mc = new MessageChannel();
var count = 1;
var t = +new Date;


console.log('start')

Promise.resolve().then(() => console.log('promise'))
requestAnimationFrame(() => console.log('animationframe'))

setTimeout(() => console.log('timeout'))

mc.port1.onmessage = (...arr) => {
console.log('ommessage')

// this will cause the setTimeout fired **before** rAF
if (count === 1) { mc.port2.postMessage(null); count = 2 }

// I guess maybe because we spend too much time so the rAF be deffered? But that's not true because if I remove the upper code and use below code won't happen that
// while(+new Date - t < 3000) {}
// or
// Promise.resolve().then(() => while(+new Date - t < 3000))
}

mc.port2.postMessage(null);

console.log('end')

如果您删除嵌套的 postMessage,它将正常工作。那么,为什么嵌套的poseMessage会改变事件循环的执行顺序呢?

顺便说一句,增加setTimeout的延迟也会使其正常工作,这意味着它仍然是一个“我们花费的时间”的问题?

最佳答案

这里似乎对 requestAnimationFrame 的作用以及所有这些事件应该如何发生存在一些误解。

因此,首先,requestAnimationFrame(callback) 会将callback 存储在map of animation-callbacks 中。 .该 map 将被清空,并在下一个 painting frame 调用其所有回调。 (第 10 步)。
这个绘画框架是HTML规范没有明确定义它应该有意发生的东西,实现者确实根据他们自己的启发式决定什么时候最好绘画。例如,Firefox 将每 60 秒调用一次,而另一方面,Chrome 将以与显示页面的显示器相同的速率调用它。

因此,如果我们从外部的 Angular 来看,我们可以说 requestAnimationFrame 只是一个 setTimeout(callback, ti​​me_until_next_painting_frame)

但是,这些回调都将在事件循环接近尾声时被调用。

另一方面,事件将在事件循环的开始处处理。特别是消息事件是触发 queue a task 的最快方式算法,因为它是 actually done synchronously .
虽然定时器总是会在之后至少触发两次事件循环迭代:

  1. 声明计时器
  2. 如果时间已过,请排队任务
  3. 触发任务

这就是为什么 onmessage 应该在超时之前触发(尽管从技术上讲,延迟超时可能会在 onmessage 之前触发)。

现在我们可以来看看 Promise.resolve() 创建的最后一个野兽,微任务。这些只是等待当前执行的当前任务。也就是说,确实调用了 Promise 的 resolve 方法的回调、事件处理程序或内联脚本,或者确实进行了触发 MutationObserver 事件的更改。

也许为此,一个简单的例子胜过千言万语:

const channel = new MessageChannel();
channel.port1.onmessage = ({ data }) => {
console.log(`enter message ${data} event handler`);
Promise.resolve()
.then( () => {
console.log(`enter microtask ${data} event handler`);
Promise.resolve()
.then( () =>
console.log(`enter sub-microtask ${data} event handler`)
)
// we could go into an infinite loop here
// which would be as blocking as a while loop
// because we never leave the current task
} )
};

// both events should happen in the same Event-Loop iteration
channel.port2.postMessage(1);
channel.port2.postMessage(2);

因此,有了所有这些,我们可以为您的代码提供一个不太高级的语言版本:

setPolyfills(); // so we can talk less high language

console.log('start');

queueMicroTask(() => console.log('promise'));

// it's basically random,
// though here we omit the very special case of
// *call-at-beginning-of-painting-frame*
setTimeout(() => console.log('animationframe'), Math.random() * 16.6);

setTimeout(() => console.log('timeout'));

queueATask(() => {
console.log('ommessage');
queueATask(() => {
console.log('onmessage');
});
});

console.log('end');


// low-level helpers
function setPolyfills() {
// Chrome has a native
if( !('queueMicroTask' in window) ) {
window.queueMicroTask = (fn) => Promise.resolve().then(fn);
}
window.queueATask = (fn) => {
const channel = new MessageChannel();
channel.port1.onmessage = e => fn();
channel.port2.postMessage('');
};
}

鉴于我们之前所说的,我们有理由期待这样的输出

// first event-loop iteration
start // synchronous
end // synchronous
promise // micro-task

// second event-loop iteration
ommessage // beginning of second event-loop iteration
//...

然后是 timeoutonmessageanimationframe,顺序随机。

timeout 可以在 onmessage 之前触发,具体取决于检查计时器的时间,它可以在第二次迭代或第三次迭代中尽快触发。< br/>onmessage 应该在第三次迭代中触发。
animationframe 可以在任何迭代中触发,从第一个 到任何其他迭代,直到下一个绘画帧。事实上,由于它实际上是在事件循环迭代结束时触发的,因此您甚至可以在消息事件之前触发它。

虽然这种非常罕见的幸运地从绘画框架的开头调用它的情况应该只会发生一次。但是Chrome has a running bug从非动画文档第一次调用 requestAnimationFrame 会立即被调用,即使这个帧实际上不是绘画帧...我担心你也遇到过这个通过做你的测试。

因此,如果我们现在应用我在此错误报告中提出的解决方法,我们也可以在 Chrome 中获得更稳定的结果:

// workaround crbug 919408 by running an infinite rAF loop
const anim = () => requestAnimationFrame(anim);
anim();
// we thus need to wait it's warmed up
setTimeout(() => {
var mc = new MessageChannel();
var count = 1;
var t = +new Date;


console.log('start')

Promise.resolve().then(() => console.log('promise'))
requestAnimationFrame(() => console.log('animationframe'))

setTimeout(() => console.log('timeout'))

mc.port1.onmessage = (...arr) => {
console.log('ommessage')
if (count === 1) { mc.port2.postMessage(null); count = 2 }
}

mc.port2.postMessage(null);

console.log('end')
}, 500);

关于javascript - 为什么嵌套的 postMessage 会导致 rAF 在 setTimeout 之前触发?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57885450/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com