Scheduler: yield() 方法
注意:此功能在 Web Workers 中可用。
Scheduler
接口的 yield()
方法用于在任务执行期间将控制权交还给 主线程,并在稍后继续执行,后续执行被调度为一个优先级任务(有关更多信息,请参阅 优先级任务调度 API)。这允许将耗时的工作分解,以保持浏览器响应。
当方法返回的 Promise 解析时,任务可以继续执行。Promise 解析的优先级默认为 "user-visible"
,但如果 yield()
调用发生在 Scheduler.postTask()
回调中,则可以继承不同的优先级。
此外,如果 yield()
调用发生在 postTask()
回调中并且 任务被中止,则 yield()
调用后的工作继续可以被取消。
语法
yield()
参数
无。
返回值
返回一个 Promise
,该 Promise 以 undefined
解析,或以 AbortSignal.reason
拒绝。
示例
功能检测
通过在 globalThis
(无论是窗口还是 worker 作用域)上测试 scheduler.yield
来检查是否支持优先级任务调度。
例如,如果 API 在当前浏览器中受支持,下面的代码将记录 "scheduler.yield: Supported"
。
// 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 之后继续时,可以完成剩余的工作。
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()
调用,以便在继续执行事件响应的其余部分之前立即显示选中状态的变化。
checkbox.addEventListener("change", async () => {
await scheduler.yield();
doSlowContentFiltering();
});
在需要将主线程上的长时间运行工作分解成一系列任务的情况下,可以重复调用 scheduler.yield()
来保持页面的响应性。
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()
调用后的继续执行与同等 priority
的 scheduler.postTask()
任务相比,行为略有不同。
scheduler.yield()
会将其任务排入一个增强的任务队列,与同等优先级级别的 scheduler.postTask()
任务相比。因此,例如,具有 "user-visible"
优先级的 scheduler.yield()
继续任务将优先于更高 "user-blocking"
优先级级别的 scheduler.postTask()
任务,但会先于同等 "user-visible"
优先级的 scheduler.postTask()
任务(在规范中,这是由任务队列的 有效优先级 定义的)。
这有时被描述为 scheduler.yield()
将其任务排入优先级级别队列的前面,而 scheduler.postTask()
任务排入末尾。这可能是一个有用的心智模型。在只有少数任务的情况下,这意味着在相同优先级下,scheduler.yield()
的继续执行将首先发生,从而为任务调度提供了额外的灵活性。例如
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 的函数将先继续执行。例如
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()
任务运行)。
例如
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()
的任务。
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 |
浏览器兼容性
加载中…