在 JS 中如何排程後臺任務?

Erucy發表於2016-05-04

關於 JavaScript,就算什麼都不記得了,也請牢記這一點:它是阻塞式的

 

想象一下,你的瀏覽器是靠一個神奇的小精靈來執行的。所有的事情都是這一個小精靈來乾的,不管是渲染 HTML、響應選單的命令、在螢幕上繪圖、處理滑鼠點選,還是執行一個 JavaScript 函式。和我們大多數人一樣,小精靈同時只能做一件事。如果我們把好多事都丟給小精靈,它們會被排到一個待處理列表中,按照順序依次執行。

 

當小精靈碰到一個 script 標籤,或者必須執行一個 JavaScript 函式的時候,所有事情都得停下來。首先得下載程式碼(如果需要的話),然後立即執行這些指令碼,再然後才能繼續執行後面的事件或者渲染操作。這個過程是必須的,因為你的指令碼可能執行任何操作:載入後續程式碼、移除所有 DOM 元素、跳轉到其他的 URL 等等。就算有兩個或者更多的小精靈,在第一個小精靈處理你的程式碼的時候,剩下那些也得停下來等著。這就是所謂的阻塞式。在長時間執行指令碼的時候,正式這種機制導致了瀏覽器會失去響應。

 

你經常希望你的 JavaScript 程式碼能夠儘快執行,因為這些程式碼會初始化元件和事件處理函式。然而,也有一些不那麼重要的後臺任務,它們並不會直接影響使用者體驗,例如:

  • 記錄分析資料
  • 把資料傳送到社交網路(或者新增 57 個“分享”按鈕)
  • 內容預讀取
  • HTML 的預處理或者預渲染

 

這些任務在時間上並不是那麼重要,不過為了讓頁面保持響應,在使用者滾動頁面或者與內容進行互動的時候,它們就不應該執行了。

 

選擇之一是使用 Web Worker,它可以在一個獨立的執行緒中並行執行。對於內容預讀取和預處理工作而言,這是個不錯的選擇,不過在 Web Worker 中,你無法直接訪問或者更新 DOM。當然你可以在你自己的程式碼中避免這一點,但你無法保證永遠不會依賴第三方程式碼,比如 Google Analytics。

 

另一種可能的方式是使用 setTimeout,比如:setTimeout(doSomething, 1);。瀏覽器會在其它立即執行的任務完成後,再去執行 doSomething() 函式。實際上,這種方式是把它放到了待處理列表的最後面。不幸的是,不管是否需要,這個函式最終都會被呼叫。

 

requestIdleCallback

 

requestIdleCallback 是一個全新的 API,它可以在瀏覽器空閒的時候去排程那些不那麼重要的後臺任務。與之類似的還有 requestAnimationFrame,可以通過呼叫一個函式在下一次重繪之前更新動畫。更多關於 requestAnimationFrame 的內容請見:使用 requestAnimationFrame 實現簡單動畫

 

我們可以通過如下方法檢測瀏覽器是否支援 requestIdleCallback:

 

也可以指定一個 options 物件作為引數,其中包含超時時間(以毫秒為單位),例如:

 

這種方式可以確保你的函式在前 3 秒之內被呼叫,不論瀏覽器是否空閒。

 

requestIdleCallback 只會呼叫你的函式一次,並傳遞一個 deadline 物件,該物件內包含如下屬性:

  • didTimeout — 如果上文提到的超時事件被觸發了,該屬性為 true
  • timeRemaining() — 該函式會返回執行任務所剩餘的毫秒數

 

timeRemaining() 會給任務執行分配不超過 50 毫秒的時間。如果任務執行超過這個時間的話,任務並不會被強行終止,不過推薦的做法是,此時你應該再次呼叫 requestIdleCallback 來排程後續的處理流程。

 

讓我們建立一個簡單的示例,按照順序執行多個任務。這些任務儲存在一個陣列中,該陣列會在函式內使用:

 

是否有什麼事情是不應該放在 requestIdleCallback 中執行的?

 

Paul Lewis 在他的部落格中進行了說明,在 requestIdleCallback 中執行的操作應該是小片段的形式,不應該執行那些執行時間無法預測的任務(比如處理 DOM 更適合使用 requestAnimationFrame 回撥)。你同樣需要當心 Promise 物件的 resolve 或 reject,因為在空閒回撥結束後,即使已經沒有剩餘時間,Promise 的回撥也會立即執行。

 

requestIdleCallback 的瀏覽器支援情況

 

requestIdleCallback 是一個試驗性質的特性,規範依然有可能會發生變化,所以如果你遇到了 API 變動的情況請不要吃驚。Chrome 47 支援該特性,也就是說應該在 2015 年結束之前。Opera 也應該很快就會支援該特性。微軟和 Mozilla 也在考慮該 API,看起來問題不大。蘋果公司一如既往地對此不置可否。如果你現在就要嚐嚐鮮,你的最佳選擇是使用 Chrome Canary(最新版本的 Chrome,沒有經過全面測試,但是其中包含了最新的特性)。(譯註:在本文翻譯時 Chrome 47 已經發布,並支援該特性)

 

之前提到的 Paul Lewis 建立了一個簡單的 requestIdleCallback 替代品。它會實現同樣功能的 API,不過它並不是完整的,因為沒有模擬瀏覽器的空閒行為檢測。它是靠 setTimeout 來完成功能的,正如上面的例子一樣。如果你想要使用該 API,又不想做物件檢測和程式碼更新的話,這也是個不錯的選擇。

 

儘管到目前為止支援還比較有限,requestIdleCallback 還是個有趣的東西,可以幫助你最大限度的優化網頁效能。不過你是怎麼想的呢?歡迎在本文下方發表你的評論。

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

在 JS 中如何排程後臺任務?

相關文章