哪个任务setTimeout或click事件在任务队列中被优先排序?

问题描述

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

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

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

预先感谢您的帮助

操作

  1. 单击按钮(任务2)
  2. -------- 1000毫秒setTimeout task1 --------
  3. 单击按钮(任务2)
  4. -------- 3000毫秒setTimeout task3 --------
  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>

解决方法

让我们看看您使用setTimeout和while循环正在做什么。

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

仅在主线程空闲时才在1秒或3秒后不完全执行回调。 setTimeout保证回调函数至少在x毫秒后执行,这对于任务1 >> 1秒,对于任务3> = 3秒。

kubectl delete pod coredns-f9fd979d6-sw2qp --namespace=kube-system

,

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

他们没有。

UI事件使用user interaction(UI) task source,它在大多数浏览器中都有自己的task-queuesetTimeout使用timer task-source,而在大多数浏览器中也都有自己的task-queue浏览器。

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

此优先级的工作方式是,在event-loop's processing model的第一步中,用户代理(UA)必须从其事件队列中选择一个将执行的 task 事件

注意:通俗地称为“宏任务” 是任何不是microtask的任务。
The microtask queue is not a task queue,虽然在事件循环处理的第一步中可以选择微任务作为主要任务,但微任务队列不能被优先排序,因为必须清空在每个microtask-checkpoints上同步 ,这在每次事件循环迭代中都可能发生多次,尤其是after the chosen task gets executed

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

还请注意they have饥饿系统,它会阻止高优先级的任务队列长时间阻止其他任务队列。


最后,我要提一个建议,让我们的Web开发人员直接处理所有这些优先事项:main thread scheduling

使用此实验功能,我们可以将代码段重写为

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');
}

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