[多线程笔试题 | 青训营笔记]
这是在第5次课《客户端容器》上课看到的一道笔试题,当时上课不是很理解,所以下课后去搜集了些资料辅助理解。
题目
以下代码在浏览器中的输出顺序、内容
<script>
const now = Date.now();
setTimeout(() => {
console.log('time10', Date.now() - now);
}, 10);
setTimeout(() => {
console.log('time30', Date.now() - now);
}, 30);
while (true) {
if (Date.now() - now >= 20) {
break;
}
}
console.log(Date.now() - now);
</script>
在Microsoft Edge浏览器的输出结果是:
为什么输出结果不是
time10 10
20
time30 30
原因分析
这是因为JavaScript是单线程语言,这意味着JavaScript在任何时候只能执行一个任务。
程序按照顺序执行下来setTimeout() 函数会先被调用,然后才会执行 while 循环,按照这个说法,那么执行结果不就是上面这个吗?
这是由于setTimeout()函数它会在指定的时间后将回调函数添加到任务队列中,等待执行,但是如果在js在执行其他任务,回调函数就必须等待当前任务执行完成。
setTimeout() 函数在等待期间不会阻塞代码的执行,因此在setTimeout()等待期间,代码按顺序执行,到while循环中,由于只有满足了Date.now() - now >= 20条件才break中断循环,while会一直占用js任务,这时10ms后回调函数必须继续等待while循环任务完成。第二个setTimeout()函数等待时间为30ms,while占用时间为20ms左右,因此不会影响。
setTimeout 函数会在指定的时间后执行回调函数,但这个时间并不是精确的。实际上,setTimeout 的回调函数可能会在指定时间后的某个时间点执行。因此时间不是精确的20ms或者30ms。
解决方案
要避免 while 循环阻止 setTimeout() 的回调函数执行,可以使用异步编程技术
以下是使用Promise的例子:
<script>
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
(async () => {
const now = Date.now();
setTimeout(() => {
console.log('time10', Date.now() - now);
}, 10);
setTimeout(() => {
console.log('time30', Date.now() - now);
}, 30);
await wait(20);
console.log(Date.now() - now);
})();
</script>
输出结果(Microsoft Edge):
在这段代码中,定义了一个 wait 函数,它返回一个 Promise 对象,这个 Promise 对象会在指定的时间后解决。
然后,使用 async/await 语法来等待 Promise 对象解决,当执行到 await wait(20) 时,代码会暂停执行,等待 20 毫秒后继续执行。在此期间,其他代码(包括 setTimeout() 的回调函数)可以正常执行。
因此,在这段代码中,setTimeout() 的回调函数不会被阻塞,它们可以在指定时间后正常执行。