使用 isInputPending() 改善 JS 排程

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

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

載入速度太快,如果網站利用 JS 轉譯內容,目前必須在載入效能和輸入回應之間取得平衡:要一次執行所有顯示所需的工作 (改善載入效能、降低輸入回應速度),或將工作分為數個較小的工作,以保持回應輸入和繪製 (降低載入效能、改善輸入回應速度)。

為免除必要影響,Facebook 提議並在 Chromium 中實作 isInputPending() API,以在不產生收益的情況下改善回應速度。根據來源試用意見回饋,我們已對 API 進行多次更新,並很高興在此宣布,API 現在預設為 Chromium 87 提供!

瀏覽器相容性

瀏覽器支援

  • 87
  • 87
  • x
  • x

自 87 版起,以 Chromium 為基礎的瀏覽器會內建 isInputPending()。 沒有任何其他瀏覽器表示意圖傳送 API。

背景

現今的 JS 生態系統中,大多數的工作都是在單一執行緒 (主執行緒) 上完成。這可為開發人員提供健全的執行模型,但如果指令碼執行時間較長,使用者體驗 (特別是回應能力) 可能會大幅降低。舉例來說,如果在輸入事件觸發時,頁面會執行大量工作,則頁面要等到作業完成後才會處理點擊輸入事件。

目前的最佳做法是將 JavaScript 分成較小的區塊,藉此處理這個問題。網頁載入時,網頁可以執行部分 JavaScript,接著傳回控制項並傳回瀏覽器。接著,瀏覽器就能檢查輸入事件佇列,看看是否有任何需要告知網頁的內容。接著,瀏覽器可以在新增的 JavaScript 區塊時,再次執行這些區塊。這麼做雖然有幫助,但也可能造成其他問題。

每次網頁傳回瀏覽器控制項時,瀏覽器需要一些時間檢查輸入事件佇列、處理事件,以及選擇下一個 JavaScript 區塊。雖然瀏覽器可在更短時間內回應事件,但網頁的整體載入時間會減慢。但如果產生太多次,網頁載入速度就會過慢假如系統產生的頻率較低,瀏覽器回應使用者事件的時間會較長,導致使用者感到不悅。無趣。

圖表顯示當您執行較長的 JS 工作時,瀏覽器在分派事件的時間較少。

Facebook 希望能瞭解我們開發新方法的載入方式,以避免這類不便。我們已向 Chrome 的好友詢問相關資訊,並提出 isInputPending() 提案。isInputPending() API 是第一個採用網路使用者輸入內容的中斷概念,可讓 JavaScript 檢查輸入內容,而不會產生瀏覽器的內容。

顯示 isInputPending() 的圖表可讓 JS 檢查使用者是否正在輸入內容,而不必將執行作業完全傳回瀏覽器。

由於這個 API 對 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)。如果您的網站需要與風格化的子頁框互動,請確保您產生的結果經常足夠。

另請留意分享事件迴圈的其他網頁。在 Chrome for Android 等平台上,多個來源共用事件迴圈很常見。如果輸入內容被分派給跨來源頁框,isInputPending() 一律不會傳回 true,因此背景執行的頁面可能會幹擾前景頁面的回應。使用 Page Visibility API 在背景執行工作時,您可能會想要減少、延後或產生收益。

建議您自行斟酌使用 isInputPending()。如果沒有可處理使用者封鎖的工作,請提高執行頻率,對事件迴圈中的其他工作展現善意。長時間執行的工作可能會有害

意見回饋:

  • 請在 is-input-pending 存放區留下對規格的意見回饋。
  • 透過 Twitter 與 @acomminos (其中一個規格作者) 聯絡。

結論

我們很高興 isInputPending() 即將上線,開發人員可以立即開始使用。這個 API 是 Facebook 首次建構新的 Web API,從構想發展到標準提案,到實際在瀏覽器中出貨。我們由衷感謝每一位協助達成目標的人,並特別對 Chrome 每位協助我們實現這個想法的人致意,讓它能送我們手中!

Will H McMahanUnsplash 網站上提供的主頁橫幅。