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. 參考實作
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 密集工作。