JavaScript 如何實現后臺計劃任務

jopen 9年前發布 | 7K 次閱讀 JavaScript開發 JavaScript

 

即使忘了 JavaScript 的一切知識,也不會忘記:它是阻塞的。

想象一下,你的瀏覽器里住著一個魔法小精靈,負責瀏覽器的正常運轉。不論渲染 HTML,響應菜單命令,屏幕渲染,處理鼠標點擊,或者執行 JavaScript 函數,所有事情都歸一個小精靈處理。它哪忙得過來,一次只能處理一件事情。如果同時丟給它一堆任務,它會列一個長長的待辦列表,按順序完成它們。

人們常常希望初始化組件和事件處理的 JavaScript 可以盡快被執行。可是,有些不太重要的后臺任務不會直接影響用戶體驗,比如:

  • 記錄統計數據
  • 發送數據到社交網絡(或添加‘分享’按鈕)
  • 預加載內容
  • 預處理或預渲染 HTML

他們對時序要求不嚴格,但是為了讓頁面仍然響應,直到用戶滾動頁面或者與內容交互時才被執行。

選擇之一是 Web Workers ,它可以在獨立的線程同時執行代碼。用于預加載和預處理再好不過,但是你沒有權限直接訪問或更新 DOM。你可以在自己的代碼中避開這點,但是無法保證第三方腳本比如 Google Analytics 永遠不需要這個。

另一個選擇是setTimeout,比如setTimeout(doSomething, 1);。一旦其它的立即執行任務執行完畢,瀏覽器將執行doSomething()函數。實際上,它被放到了待辦列表的底部。不幸的是,函數將被調用,而不顧處理需求。

#requestIdleCallback

requestIdleCallback 是新API,當瀏覽器稍作喘息的時候,用來執行不太重要的后臺計劃任務。 難免讓人想起 requestAnimationFrame ,在下次重繪之前,執行函數更新動畫。 想了解更多戳這里: 使用 requestAnimationFrame 做簡單的動畫

requestIdleCallback特性監測:

if ('requestIdleCallback' in window) {
  // requestIdleCallback supported
  requestIdleCallback(backgroundTask);
}
else {
  // no support - do something else
  setTimeout(backgroundTask1, 1);
  setTimeout(backgroundTask2, 1);
  setTimeout(backgroundTask3, 1);
}

也可以指定配置參數對象,比如 timeout,

requestIdleCallback(backgroundTask, { timeout: 3000; }); 

確保函數在3秒之內調用,不管瀏覽器是否空閑。

deadline對象傳入以下參數時,requestIdleCallback僅執行一次回調:

  • didTimeout—— 如果可選的 timeout 觸發,則設置為 true
  • timeRemaining()—— 函數返回執行任務剩余的毫秒數
    timeRemaining()最多分配50ms用于任務的執行,超過這個限制,也不會停止任務,但是,最好重新調用requestIdleCallback安排進一步的處理。

我們來創建一個簡單的例子,讓幾個任務按序執行。任務的函數引用儲存在數組中:

//待執行的函數數組
var task = [
    background1,
    background2,
    background3
];

if ('requestIdleCallback' in window) {
  //支持 requestIdleCallback
  requestIdleCallback(backgroundTask);
}
else {
  //不支持 —— 立刻執行所有任務
  while (task.length) {
    setTimeout(task.shift(), 1);
  }
}

//requestIdleCallback 回調函數
function backgroundTask(deadline) {

  //如果存在,執行下一個任務
  while (deadline.timeRemaining() > 0 && task.length > 0) {
    task.shift()();
  }

  //需要的話,安排進一步任務
  if (task.length > 0) {
    requestIdleCallback(backgroundTask);
  }
}

#一次 requestIdleCallback 之間不應該做什么?

Paul Lewis 在 他的文章 中提到,一次 requestIdleCallback 執行的任務應該切成小塊。它不適用于不可預知時間的情況(比如操作 DOM,使用 requestAnimationFrame 回調更好些)。resolving(或者 rejecting)Promises 時也要謹慎,即使沒有更多的剩余時間,空閑回調完成之后,回調函數也將立即執行。

#requestIdleCallback 瀏覽器支持情況

requestIdleCallback是試驗性特性,規范仍不穩定,碰到 API 變更時不足為奇。Chrome 47 已支持… 2015年結束前應該可用了。Opera 應該會緊跟其后。Microsoft 和 Mozilla 都在考慮 API 是否應該支持 Promises 。Apple 像往常一樣不鳥。

Paul Lewis(上文提到的)寫了一個簡單的 requestIdleCallback shim ,它可以模擬瀏覽器的空閑監測行為,但不是一個 polyfill( shim 和 polyfill 的區別 )。

requestIdleCallback shim代碼如下:

/*!
 * Copyright 2015 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

/*
 * @see https://developers.google.com/web/updates/2015/08/using-requestidlecallback
 */
window.requestIdleCallback = window.requestIdleCallback ||
  function (cb) {
    var start = Date.now();
    return setTimeout(function () {
      cb({ 
        didTimeout: false,
        timeRemaining: function () {
          return Math.max(0, 50 - (Date.now() - start));
        }
      });
    }, 1);
  }

window.cancelIdleCallback = window.cancelIdleCallback ||
  function (id) {
    clearTimeout(id);
  }

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!