具代表性的面試主題

如何安全使用 scheduler.postTask,並為不支援的瀏覽器設計降級?

前端困難
OfferCC 編輯團隊發佈 更新

題幹

一個 Web 應用程式需要把搜尋索引、可見區域渲染和低優先級預取放到主執行緒背景處理。請用 scheduler.postTask 設計排程器,並說明任務優先級、取消、scheduler.yield、瀏覽器不支援時的降級,以及如何證明互動沒有變慢。

1. 題目

頁面會在使用者輸入時更新搜尋結果,同時還要解析離線索引並預取下一頁。請設計一個基於 Prioritized Task Scheduling API 的任務排程層:使用者可見更新應及時執行,預取不能長期阻塞輸入;說明 scheduler.postTask()、scheduler.yield()、取消訊號和不支援此 API 時的行為。

2. 約束與釐清

  • 所有任務都在同一個視窗或 Worker 的事件迴圈中執行,長同步函式仍會阻塞目前執行緒。
  • 任務至少分為 user-blocking、user-visible 和 background 三種優先級。
  • 任務可能在捲動、路由切換或新輸入後失去價值,需要可取消或降低優先級。
  • 必須保留可運作的降級路徑,不能把瀏覽器支援當成業務正確性的前提。

3. 核心思路

scheduler.postTask(callback, options) 把回呼排入帶優先級的任務佇列,並回傳 Promise;priority 可以是 user-blocking、user-visible 或 background。需要取消時傳入 AbortSignal;共享 TaskController 還能改變尚未執行任務的優先級。scheduler.yield() 讓非同步函式主動把控制權交還瀏覽器,再繼續後半段工作。

優先級只影響排程順序,不會搶佔已經執行的 JavaScript。每個回呼仍應切成短片段,並在批次之間讓出控制權。任務排程 API 不可用時,可採用小批次 setTimeout、MessageChannel 或既有框架排程器,並保留相同的取消與過期語意。

4. 參考實作

javascript
const scheduler = globalThis.scheduler;

function scheduleWork(task, priority, signal) {
  if (scheduler?.postTask) {
    return scheduler.postTask(task, { priority, signal });
  }

  return new Promise((resolve, reject) => {
    const run = () => {
      if (signal?.aborted) {
        reject(signal.reason);
        return;
      }
      Promise.resolve().then(task).then(resolve, reject);
    };
    setTimeout(run, priority === "background" ? 50 : 0);
  });
}

async function indexInChunks(items, signal) {
  for (let i = 0; i < items.length; i += 100) {
    await scheduleWork(() => buildIndex(items.slice(i, i + 100)),
      "background", signal);
    if (scheduler?.yield && i + 100 < items.length) {
      await scheduler.yield({ signal });
    }
  }
}

5. 效能與正確性

任務優先級不會改變程式結果,也不會打斷正在執行的回呼;它只能影響尚未開始任務的相對順序。scheduler.postTask() 回傳的 Promise 會在回呼回傳值完成時解決,回呼拋錯或訊號取消時,呼叫端應處理 rejection。

真正的效能邊界是任務時長和總工作量:把 200 毫秒的同步迴圈放進低優先級佇列仍會阻塞輸入。應按批次測量 Long Tasks、輸入延遲和取消命中率,調整批次大小;CPU 密集且可並行的工作可考慮 Worker,不能只靠優先級掩蓋主執行緒飢餓。

6. 追問與陷阱

  • 檢測 globalThis.scheduler?.postTask,不要只根據瀏覽器品牌或版本猜測支援情況。
  • AbortSignal 只能取消尚未開始或會觀察訊號的工作;已經執行的同步回呼不會被強制中斷。
  • 動態改變優先級不等於搶佔;若任務已出佇列,仍需在業務層設定過期版本,避免寫入過時結果。
  • 降級到 setTimeout 不能重現原生優先級的全部語意,必須驗證使用者可見任務、背景任務和取消路徑仍能運作。

7. 延伸閱讀

可以比較 scheduler.postTask() 與 requestIdleCallback()、MessageChannel 和框架排程器:前者提供明確優先級與取消,閒置回呼依賴閒置時機,訊息通道只提供排隊,框架排程器還可能加入元件生命週期語意。選型時應結合相容性、任務類型和測量結果。

8. 面試評分點

能解釋優先級邊界

應說明三種優先級、Promise 回傳值和「只排序尚未開始任務」的語意,不能把它描述成執行緒搶佔。

能設計可取消任務

應使用 AbortSignal 或 TaskController,並討論已開始回呼無法強制終止、結果版本需要過期檢查。

能寫出漸進式降級

應先做能力偵測,再提供計時器、訊息通道或框架排程器降級,同時保留相同的任務分級和取消行為。

能用資料證明收益

應測量 Long Tasks、輸入延遲、批次時長和取消命中率,並指出 Worker 才能隔離真正的 CPU 密集工作。

公開來源

同類題目

資源

產品比較

每份比較均依官方資料整理,並標示複核日期。

查看完整比較