任务队列中哪个任务(setTimeout 或 click 事件)优先?

问题描述 投票:0回答:2

我正在学习执行栈、任务队列和事件循环机制。

我连续单击按钮,直到主线程可用(就在函数 a() 完成之前),如下所示。
我以为click(UI)事件和setTimeout使用同一个队列,称为Macrotask或Task Queue,所以当我在1s和3s之间点击按钮时,我认为task2的日志是在task1和task2之间打印的。但结果却并非如此。 Task2(点击事件)总是先打印,setTimeout 事件(task1,task3)在点击事件之后打印。

所以我想知道单击事件是否使用与 setTimeout 不同的队列机制,或者单击事件优先于 setTimeout。

提前感谢您的帮助

操作

  1. 单击按钮(任务2)
  2. --------1000ms setTimeout 任务1--------
  3. 单击按钮(任务2)
  4. --------3000ms setTimeout 任务3--------
  5. 单击按钮(任务2)
  6. --------6000ms主线程现已可用--------

我的期望日志订单

fn a done
task2 (click) done
task1 (setTimeout 1000ms) done
task2 (click) done
task3 (setTimeout 3000ms) done
task2 (click) done

结果日志顺序

fn a done
task2 (click) done
task2 (click) done
task2 (click) done
task1 (setTimeout 1000ms) done
task3 (setTimeout 3000ms) done

代码

const btn = document.querySelector('button');
btn.addEventListener('click', function task2() {
  console.log('task2 (click) done');
});

function a() {
  setTimeout(function task1() { 
    console.log('task1 (setTimeout 1000ms) done');
  }, 1000);

  setTimeout(function task3() { 
    console.log('task3 (setTimeout 3000ms) done');
  }, 3000);

  // hold main thread for 6000ms(*1)
  const startTime = new Date();
  while (new Date() - startTime < 6000);

  console.log('fn a done');
}

a();
<button>button</button>
<script src="main.js"></script>

javascript event-loop task-queue
2个回答
1
投票

我认为 click(UI) 事件和 setTimeout 使用相同的队列

他们没有。

UI 事件使用 用户交互 (UI) 任务源,在大多数浏览器中都有自己的 任务队列

setTimeout
使用定时器任务源,它也有自己的 任务队列 在大多数浏览器中。

虽然规范没有要求,但 UI 任务源在几乎所有浏览器中具有最高优先级之一,而计时器具有最低优先级之一。

这种优先级的工作原理是,在事件循环处理模型的第一步,用户代理(UA)必须选择其要执行的任务的事件队列之一。

注意: 通俗地称为 “宏任务” 是任何不是 微任务的任务。
微任务队列不是任务队列,虽然在事件循环处理的第一步中可以选择微任务作为主任务,但微任务队列无法确定优先级,因为它必须被清空同步地在每个微任务检查点,这可能在每个事件循环迭代期间发生多次,特别是每次JS调用堆栈被清空时

所以在这里,当

while
循环完成阻塞事件循环并且下一次迭代开始时,UA 将必须从哪个任务队列中选择它应该选择下一个任务。 它将在其UI任务队列中看到有新事件在等待,并执行它们,因为它们具有更高的优先级。
然后,当它们全部执行完毕时,它将选择计时器队列并按照计划时间的顺序执行它们。

还请注意,他们有一个饥饿系统,可以防止高优先级任务队列阻塞其他任务队列太长时间。


最后,我应该提一下,有一个建议让我们的网络开发人员直接处理所有这些优先级问题:主线程调度

使用这个实验性功能,我们可以将代码片段重写为

if( !("scheduler" in window) ) {
  console.error("Your browser doesn't support the postTask API");
  console.error("Try enabling the Experimental Web Platform features in chrome://flags");

}
else {
  scheduler.postTask(() => { 
    console.log('task1 (background) done');
  }, { priority: "background" } );

  scheduler.postTask(() => { 
    console.log('task2 (background) done');
  }, { priority: "background" } );

  // hold main thread for 6000ms(*1)
  const startTime = new Date();
  while (new Date() - startTime < 2000);
  scheduler.postTask(() => { 
    console.log('task3 (user-blocking) done');
  }, { priority: "user-blocking" } );

  console.log('synchronous done');
}

并且看到最终任务首先被执行。


0
投票

让我们看看你用 setTimeout 和 while 循环做了什么。

如果运行代码片段,您将看到 1 秒和 3 秒超时的时间戳基本相同。这是因为 while 循环会阻塞主线程 6 秒,直到可以运行 setTimeout 中的函数。

回调不会在 1 秒或 3 秒后执行,而是仅在主线程空闲时执行。 setTimeout 保证回调函数在至少 x 毫秒后执行,对于任务 1 来说 >= 1 秒,对于任务 3 来说 >= 3 秒。

function a() {
  setTimeout(function task1() { 
    console.log('task1 (setTimeout 1000ms) done ' + new Date());
  }, 1000);

  setTimeout(function task3() { 
    console.log('task3 (setTimeout 3000ms) done ' + new Date());
  }, 3000);

  // hold main thread for 6000ms(*1)
  const startTime = new Date();
  while (new Date() - startTime < 6000);

  console.log('fn a done');
}

a();

© www.soinside.com 2019 - 2024. All rights reserved.