使用 isInputPending() 改善 JS 排程

新的 JavaScript API,可避免您在載入效能和輸入回應速度之間做出取捨。

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

快速載入很困難。目前,使用 JS 算繪內容的網站必須在載入效能和輸入回應性之間做出取捨:要不就是一次執行所有顯示作業所需的工作 (載入效能較佳,輸入回應性較差),要不就是將工作分割成較小的任務,以便持續回應輸入和繪製 (載入效能較差,輸入回應性較佳)。

為避免需要做出這種取捨,Facebook 在 Chromium 中提出並實作 isInputPending() API,以便在不產生讓步的情況下改善回應速度。我們已根據來源試用意見回饋,對 API 做出了多項更新,很高興在此宣布,這個 API 現在預設為在 Chromium 87 中推出!

瀏覽器相容性

瀏覽器支援

  • Chrome:87。
  • Edge:87。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

isInputPending() 已在 87 以上版本的 Chromium 瀏覽器中提供。沒有其他瀏覽器發出傳送 API 的意圖。

背景

現今 JS 生態系統中的大部分工作都是在單一執行緒 (主執行緒) 上完成。這為開發人員提供了健全的執行模型,但如果指令碼長時間執行,使用者體驗可能會大幅下降。舉例來說,如果網頁在觸發輸入事件時執行大量工作,則網頁會在該工作完成後才處理點擊輸入事件。

目前的最佳做法是將 JavaScript 分割成較小的區塊,以便處理這個問題。在網頁載入期間,網頁可以執行一些 JavaScript,然後交出並將控制權交還給瀏覽器。然後瀏覽器就能檢查輸入事件佇列,看看是否有任何需要通知網頁的內容。接著,瀏覽器就可以在新增 JavaScript 區塊時,繼續執行這些區塊。這雖然有幫助,但可能會造成其他問題。

每次網頁將控制權交還給瀏覽器時,瀏覽器都需要花費一些時間來檢查輸入事件佇列、處理事件,以及接收下一個 JavaScript 區塊。瀏覽器能更快回應事件,但網頁的整體載入時間會減慢。如果我們太常讓出控制權,頁面載入速度就會過慢。如果產生的頻率降低,瀏覽器需要較長的時間才能回應使用者事件,讓使用者感到不悅。無趣。

這張圖表顯示,當您執行長時間的 JS 工作時,瀏覽器可用來調度事件的時間較少。

在 Facebook,我們想瞭解如果新的載入方法能消除此次的取捨,應該會是什麼樣子。我們已與 Chrome 的好友聯絡,提出了 isInputPending() 的提案。isInputPending() API 是第一個使用網頁上使用者輸入中斷概念的 API,可讓 JavaScript 檢查輸入內容,而無須交由瀏覽器處理。

這張圖表顯示 isInputPending() 可讓 JS 檢查是否有待處理的使用者輸入內容,而不必將執行作業完全交由瀏覽器處理。

由於 API 受到關注,我們與 Chrome 的同事合作,在 Chromium 中實作並發布這項功能。在 Chrome 工程師的協助下,我們已在來源試用版中推出修補程式 (這是 Chrome 在完全發布 API 前,測試變更並取得開發人員意見的一種方式)。

我們已從來源試用和 W3C Web Performance Working Group 的其他成員取得意見回饋,並對 API 實作變更。

範例:更有效率的排程器

假設您需要執行許多顯示阻斷工作才能載入網頁,例如從元件產生標記、分離素數,或只是繪製酷炫的載入旋轉圖示。每個項目都會細分為個別工作項目。使用排程器模式,讓我們勾勒出如何在假設的 processWorkQueue() 函式中處理工作:

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

透過 setTimeout() 在稍後透過新宏工作執行 processWorkQueue(),可讓瀏覽器在管理相對不受干擾的執行作業的同時,仍能對輸入內容做出某種回應 (可在工作恢復前執行事件處理常式)。不過,如果有其他工作需要控制事件迴圈,可能會讓我們長時間處於取消排程狀態,或是讓事件延遲時間增加至 QUANTUM 毫秒。

這還不錯,但我們可以做得更好嗎?沒錯!

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

透過呼叫 navigator.scheduling.isInputPending(),我們可以更快回應輸入內容,同時確保顯示封鎖作業不會中斷。如果在工作完成前,我們不想處理輸入內容以外的任何內容 (例如繪圖),也可以輕鬆增加 QUANTUM 的長度。

根據預設,系統不會從 isInputPending() 傳回「連續」事件。包括 mousemovepointermove 和其他頻道。如果您也對這些項目感興趣,我們也樂意提供收益。只要將 includeContinuous 設為 true,即可為 isInputPending() 提供物件,讓我們繼續進行:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

大功告成!React 等架構會使用類似的邏輯,在核心排程程式庫中建構 isInputPending() 支援功能。希望這項功能可讓使用這些架構的開發人員在不需要大幅重寫的情況下,就能在幕後享有 isInputPending() 的優點。

讓出資源不一定是壞事

值得注意的是,減少產量並非適用於所有用途的正確解決方案。除了處理輸入事件,還有許多原因會將控制權交還給瀏覽器,例如在網頁上執行算繪和其他指令碼。

在某些情況下,瀏覽器無法正確歸因待處理的輸入事件。具體來說,為跨來源 iframe 設定複雜的片段和遮罩可能會回報偽陰性 (也就是說,當指定這些影格時,isInputPending() 可能會意外傳回 false)。如果網站確實需要與圖形化子畫面互動,請務必確保頻率足夠頻繁。

請留意其他共用事件迴圈的網頁。在 Android 版 Chrome 等平台上,多個來源共用事件迴圈的情況相當常見。如果輸入內容是分派至跨來源影格,isInputPending() 一律不會傳回 true,因此背景頁面可能會幹擾前景頁面的回應。您可能會想在使用 Page Visibility API 執行背景工作時,更頻繁地減少、延後或讓出工作。

建議您斟酌使用 isInputPending()。如果沒有需要執行的使用者封鎖工作,請盡量頻繁地在事件迴圈中產生,以免影響其他人。長時間的工作可能會造成不良影響

意見回饋

結論

我們很高興 isInputPending() 即將推出,開發人員可以立即開始使用。這是 Facebook 首次建構新的網頁 API,並將其從構思孵育階段,轉變為標準提案,進而實際在瀏覽器中推出。感謝所有幫助我們達到這個目標的人,並特別感謝 Chrome 的每一位貢獻一己之力,協助我們實現這個想法並付諸實行的 Chrome 使用者!

主頁橫幅相片由 Will H McMahan 提供,取自 Unsplash