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

瀏覽器相容性

另見