Service Worker初體驗

jopen 8年前發布 | 9K 次閱讀 瀏覽器 前端技術

在2014年,W3C公布了service worker的草案,service worker提供了很多新的能力,使得web app擁有與native app相同的離線體驗、消息推送體驗。service worker是一段腳本,與web worker一樣,也是在后臺運行。作為一個獨立的線程,運行環境與普通腳本不同,所以不能直接參與web交互行為。native app可以做到離線使用、消息推送、后臺自動更新,service worker的出現是正是為了使得web app也可以具有類似的能力。

service worker可以:

  1. 后臺消息傳遞
  2. 網絡代理,轉發請求,偽造響應

  3. 離線緩存

  4. 消息推送

  5. … …

本文以資源緩存為例,說明一下service worker是如何工作的。

生命周期

先來看一下一個service worker的運行周期

上圖是service worker生命周期,出處 http://www.html5rocks.com/en/tutorials/service-worker/introduction/

圖中可以看到,一個service worker要經歷以下過程:

1.  安裝

2.  激活,激活成功之后,打開chrome://inspect/#service-workers可以查看到當前運行的service worker

3. 監聽fetch和message事件,下面兩種事件會進行簡要描述

4. 銷毀,是否銷毀由瀏覽器決定,如果一個service worker長期不使用或者機器內存有限,則可能會銷毀這個worker

fetch事件

在頁面發起http請求時,service worker可以通過fetch事件攔截請求,并且給出自己的響應。w3c提供了一個新的fetch api,用于取代XMLHttpRequest,與XMLHttpRequest最大不同有兩點:

1. fetch()方法返回的是Promise對象,通過then方法進行連續調用,減少嵌套。ES6的Promise在成為標準之后,會越來越方便開發人員。

2. 提供了Request、Response對象,如果做過后端開發,對Request、Response應該比較熟悉。前端要發起請求可以通過url發起,也可以使用Request對象發起,而且Request可以復用。但是Response用在哪里呢?在service worker出現之前,前端確實不會自己給自己發消息,但是有了service worker,就可以在攔截請求之后根據需要發回自己的響應,對頁面而言,這個普通的請求結果并沒有區別,這是Response的一處應用。

下面是在 http://www.sitepoint.com/introduction-to-the-fetch-api/ 中,作者利用fetch api通過fliker的公開api獲取圖片的例子,注釋中詳細解釋了每一步的作用:

/* 由于是get請求,直接把參數作為query string傳遞了 */
var URL = 'https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=your_api_key&format=json&nojsoncallback=1&tags=penguins';
 
function fetchDemo() {
  // fetch(url, option)支持兩個參數,option中可以設置header、body、method信息
  fetch(URL).then(function(response) {
    // 通過promise 對象獲得相應內容,并且將響應內容按照json格式轉成對象,json()方法調用之后返回的依然是promise對象
    // 也可以把內容轉化成arraybuffer、blob對象
    return response.json();
  }).then(function(json) {
    // 渲染頁面
    insertPhotos(json);
  });
}
 
fetchDemo();

fetch api與XMLHttpRequest相比,更加簡潔,并且提供的功能更全面,資源獲取方式比ajax更優雅。兼容性方面:chrome 42開始支持,對于舊瀏覽器,可以通過官方維護的polyfill支持。

message事件

頁面和serviceWorker之間可以通過posetMessage()方法發送消息,發送的消息可以通過message事件接收到。

這是一個雙向的過程,頁面可以發消息給service worker,service worker也可以發送消息給頁面,由于這個特性,可以將service worker作為中間紐帶,使得一個域名或者子域名下的多個頁面可以自由通信。

這里是一個小的頁面之間通信demo https://nzv3tos3n.qnssl.com/message/msg-demo.html

利用service workder緩存文件

下面介紹一個利用service worker緩存離線文件的例子準備index.js,用于注冊service-worker

if (navigator.serviceWorker) {
    navigator.serviceWorker.register('service-worker.js').then(function(registration) {
        console.log('service worker 注冊成功');
    }).catch(function (err) {
        console.log('servcie worker 注冊失敗')
    });
}

在上述代碼中,注冊了service-worker.js作為當前路徑下的service worker。由于service worker的權限很高,所有的代碼都需要是安全可靠的,所以只有https站點才可以使用service worker,當然localhost是一個特例。

注冊完畢,現在開始寫service-worker.js代碼。

根據前面的生命周期圖,在一個新的service worker被注冊以后,首先會觸發install事件,在service-workder.js中,可以通過監聽install事件進行一些初始化工作,或者什么也不做。

因為我們是要緩存離線文件,所以可以在install事件中開始緩存,但是只是將文件加到caches緩存中,真正想讓瀏覽器使用緩存文件需要在fetch事件中攔截

var cacheFiles = [
    'about.js',
    'blog.js'
];
self.addEventListener('install', function (evt) {
    evt.waitUntil(
        caches.open('my-test-cahce-v1').then(function (cache) {
            return cache.addAll(cacheFiles);
        })
    );
});

首先定義了需要緩存的文件數組cacheFile,然后在install事件中,緩存這些文件。

evt是一個InstallEvent對象,繼承自ExtendableEvent,其中的waitUntil()方法接收一個promise對象,直到這個promise對象成功resolve之后,才會繼續運行service-worker.js。

caches是一個CacheStorage對象,使用open()方法打開一個緩存,緩存通過名稱進行區分。

獲得cache實例之后,調用addAll()方法緩存文件。

這樣就將文件添加到caches緩存中了,想讓瀏覽器使用緩存,還需要攔截fetch事件

// 緩存圖片
self.addEventListener('fetch', function (evt) {
    evt.respondWith(
        caches.match(evt.request).then(function(response) {
            if (response) {
                return response;
            }
            var request = evt.request.clone();
            return fetch(request).then(function (response) {
                if (!response && response.status !== 200 && !response.headers.get('Content-type').match(/image/)) {
                    return response;
                }
                var responseClone = response.clone();
                caches.open('my-test-cache-v1').then(function (cache) {
                    cache.put(evt.request, responseClone);
                });
                return response;
            });
        })
    )
});

通過監聽fetch事件,service worker可以返回自己的響應。

首先檢緩存中是否已經緩存了這個請求,如果有,就直接返回響應,就減少了一次網絡請求。否則由service workder發起請求,這時的service workder起到了一個中間代理的作用。

service worker請求的過程通過fetch api完成,得到response對象以后進行過濾,查看是否是圖片文件,如果不是,就直接返回請求,不會緩存。

如果是圖片,要先復制一份response,原因是request或者response對象屬于stream,只能使用一次,之后一份存入緩存,另一份發送給頁面。這就是service worker的強大之處:攔截請求,偽造響應。fetch api在這里也起到了很大的作用。

service worker的更新很簡單,只要service-worker.js的文件內容有更新,就會使用新的腳本。但是有一點要注意:舊緩存文件的清除、新文件的緩存要在activate事件中進行,因為可能舊的頁面還在使用之前的緩存文件,清除之后會失去作用。

在初次使用service worker的過程中,也遇到了一些問題,下面是其中兩個

問題1. 運行時間

service worker并不是一直在后臺運行的。在頁面關閉后,瀏覽器可以繼續保持service worker運行,也可以關閉service worker,這取決與瀏覽器自己的行為。所以不要定義一些全局變量,例如下面的代碼(來自 https://jakearchibald.com/2014/service-worker-first-draft/ ):

var hitCounter = 0;
 
this.addEventListener('fetch', function(event) {
  hitCounter++;
  event.respondWith(
    new Response('Hit number ' + hitCounter)
  );
});

返回的結果可能是沒有規律的:1,2,1,2,1,1,2….,原因是hitCounter并沒有一直存在,如果瀏覽器關閉了它,下次啟動的時候hitCounter就賦值為0了這樣的事情導致調試代碼困難,當你更新一個service worker以后,只有在打開新頁面以后才可能使用新的service worker,在調試過程中經常等上一兩分鐘才會使用新的,比較抓狂。

問題2. 權限太大

當service worker監聽fetch事件以后,對應的請求都會經過service worker。通過chrome的network工具,可以看到此類請求會標注:from service worker。如果service worker中出現了問題,會導致所有請求失敗,包括普通的html文件。所以service worker的代碼質量、容錯性一定要很好才能保證web app正常運行。

參考文章:

1. http://www.html5rocks.com/en/tutorials/service-worker/introduction/

2. http://www.sitepoint.com/introduction-to-the-fetch-api/

3. https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent

4. https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent

5. https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage

來自: http://www.alloyteam.com/2016/01/9274/

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