Scheduler: yield() 方法

可用性有限

此特性不是基线特性,因为它在一些最广泛使用的浏览器中不起作用。

注意:此功能在 Web Workers 中可用。

Scheduler 接口的 yield() 方法用于在任务执行期间将控制权交还给 主线程,并在稍后继续执行,后续执行被调度为一个优先级任务(有关更多信息,请参阅 优先级任务调度 API)。这允许将耗时的工作分解,以保持浏览器响应。

当方法返回的 Promise 解析时,任务可以继续执行。Promise 解析的优先级默认为 "user-visible",但如果 yield() 调用发生在 Scheduler.postTask() 回调中,则可以继承不同的优先级。

此外,如果 yield() 调用发生在 postTask() 回调中并且 任务被中止,则 yield() 调用后的工作继续可以被取消。

语法

js
yield()

参数

无。

返回值

返回一个 Promise,该 Promise 以 undefined 解析,或以 AbortSignal.reason 拒绝。

示例

功能检测

通过在 globalThis(无论是窗口还是 worker 作用域)上测试 scheduler.yield 来检查是否支持优先级任务调度。

例如,如果 API 在当前浏览器中受支持,下面的代码将记录 "scheduler.yield: Supported"

js
// Check for support before using.
if (globalThis.scheduler?.yield) {
  console.log("scheduler.yield: Supported");
} else {
  console.error("scheduler.yield: NOT Supported");
}

基本用法

可以通过 await scheduler.yield() 来分解耗时任务。该函数返回一个 Promise,将控制权交还给主线程,以便浏览器执行其他待处理的工作,例如响应用户输入(如果需要)。浏览器会调度一个后续任务来解析 Promise,届时代码可以从中断处继续执行。

例如,如果按钮上的 click 事件监听器会导致加载和显示新页面内容的繁重工作,那么在完成该工作之前,用户将不会看到任何视觉反馈来表明他们的按钮点击已被页面注册。可以在事件监听器中插入 scheduler.yield(),以便显示快速反馈(例如加载图标),然后当执行在 yield 之后继续时,可以完成剩余的工作。

js
button.addEventListener("click", async () => {
  // Provide immediate feedback so the user knows their click was received.
  showSpinner();
  await scheduler.yield();
  // Do longer processing
  doSlowContentSwap();
});

有时,提供快速的交互反馈与默认 UI 就足够了。例如,如果复选框上的 change 事件监听器会触发页面内容的缓慢过滤,则可以插入 scheduler.yield() 调用,以便在继续执行事件响应的其余部分之前立即显示选中状态的变化。

js
checkbox.addEventListener("change", async () => {
  await scheduler.yield();
  doSlowContentFiltering();
});

在需要将主线程上的长时间运行工作分解成一系列任务的情况下,可以重复调用 scheduler.yield() 来保持页面的响应性。

js
function doWork(value) {
  console.log(`work chunk ${value}`);
}

const workList = [0, 1, 2, 3, 4];

for (const work of workList) {
  doWork(work);
  await scheduler.yield();
}

Yield 优先级

scheduler.yield() 返回的 Promise 相对于其他任务的解析顺序基于隐式的 任务优先级

默认情况下,scheduler.yield()"user-visible" 优先级运行。然而,scheduler.yield() 调用后的继续执行与同等 priorityscheduler.postTask() 任务相比,行为略有不同。

scheduler.yield() 会将其任务排入一个增强的任务队列,与同等优先级级别的 scheduler.postTask() 任务相比。因此,例如,具有 "user-visible" 优先级的 scheduler.yield() 继续任务将优先于更高 "user-blocking" 优先级级别的 scheduler.postTask() 任务,但会先于同等 "user-visible" 优先级的 scheduler.postTask() 任务(在规范中,这是由任务队列的 有效优先级 定义的)。

这有时被描述为 scheduler.yield() 将其任务排入优先级级别队列的前面,而 scheduler.postTask() 任务排入末尾。这可能是一个有用的心智模型。在只有少数任务的情况下,这意味着在相同优先级下,scheduler.yield() 的继续执行将首先发生,从而为任务调度提供了额外的灵活性。例如

js
scheduler.postTask(() => console.log("user-visible postTask"));
scheduler.postTask(() => console.log("user-blocking postTask"), {
  priority: "user-blocking",
});
await scheduler.yield();
console.log("user-visible yield");

将打印以下内容

user-blocking postTask
user-visible yield
user-visible postTask

然而,在存在多个 scheduler.yield() 调用时,scheduler.yield() 继续任务进入一个增强优先级 *队列* 的区别变得很重要,因为第二个 scheduler.yield() 任务不会在已在队列中的任务之前运行。

如果一个函数比第二个函数先 yield 其工作,那么第一个 yield 的函数将先继续执行。例如

js
async function first() {
  console.log("starting first function");
  await scheduler.yield();
  console.log("ending first function");
}

async function second() {
  console.log("starting second function");
  await scheduler.yield();
  console.log("ending second function");
}

first();
second();

将打印以下内容

starting first function
starting second function
ending first function
ending second function

继承任务优先级

scheduler.postTask() 任务中的 scheduler.yield() 调用将继承该任务的优先级。例如,在低优先级 "background" 任务中,scheduler.yield() 后的工作默认也将被调度为 "background"(但同样,会插入到增强的 "background" 优先级队列中,因此会先于任何 "background" postTask() 任务运行)。

例如

js
async function backgroundWork() {
  scheduler.postTask(() => console.log("background postTask"), {
    priority: "background",
  });
  scheduler.postTask(() => console.log("user-visible postTask"), {
    priority: "user-visible",
  });
  // yield() inherits "background" priority from surrounding task.
  await scheduler.yield();
  console.log("default-background yield");
}

await scheduler.postTask(backgroundWork, { priority: "background" });

将打印以下内容

user-visible postTask
default-background yield
background postTask

scheduler.yield() 的继续执行将继承包含的 scheduler.postTask() 任务的任何优先级,包括任务优先级是否 被动态更改

中止 yield

与设置优先级类似,scheduler.yield() 调用本身无法直接中止,但它将继承来自外部 scheduler.postTask() 任务的中止信号。中止任务也将中止其中的任何待处理 yield。

此示例使用 TaskController中止包含 scheduler.yield() 的任务

js
const taskController = new TaskController();

function firstHalfOfWork() {
  console.log("first half of work");
  taskController.abort("cancel work");
}

function secondHalfOfWork() {
  // Never runs.
  console.log("second half of work");
}

scheduler.postTask(
  async () => {
    firstHalfOfWork();
    await scheduler.yield();
    secondHalfOfWork();
  },
  { signal: taskController.signal },
);

这个例子有些牵强,因为它总是在任务本身内触发 taskController.abort() 调用,但 abort() 调用可能来自任何地方。例如,它可能由用户按下“取消”按钮触发。

在这种情况下,abort() 发生在 scheduler.postTask() 任务已经开始之后(打印了 "first half of work"),但是 yield 调用继承了 中止信号,因此 await scheduler.yield() 调用将抛出,中止原因为 "cancel work"

requestIdleCallback() 中使用 yield()

当从回调函数内部调用时,scheduler.yield() 调用也从 Window.requestIdleCallback() 继承其优先级。在这种情况下,会继承 "background" 优先级值。但请注意,requestIdleCallback() 回调中的 scheduler.yield() 调用是不可中止的。

规范

规范
优先任务调度
# dom-scheduler-yield

浏览器兼容性

另见