pwa-之service worker 基本概念

pwa-之service worker 基本概念
pwa-之service worker 離線文件處理css

學習service worker 基本概念

在本章,將涵蓋如下內容html

  • service worker準備工做
  • 註冊service worker
  • 註冊service worker細節
  • 調試
  • 出現錯誤時提供穩定版本
  • 建立mock響應
  • 處理請求超時

簡介

若是你是一個旅行愛好者,應該會常常陷入沒有網絡的狀況。這是使人沮喪的。特別是你有事情的時候。jquery

service worker是一個在==瀏覽器後臺==運行的腳本。不管網絡鏈接如何,可以使用Web應用程序意味着用戶能夠在飛機,地鐵或鏈接受限或不可用的地方不間斷地操做。 該技術將有助於提升客戶端的工做效率,並將提升應用程序的可用性。git

經過service worker,咱們能夠預先緩存網站的某些資源。 咱們做爲資源引用的是JavaScript文件,CSS文件,圖像和一些字體。 這將有助於咱們加快加載時間,而沒必要每次訪問同一網站時都必須從服務器獲取。 固然,最重要的是,當咱們網絡不順暢時,這些資源將可供咱們使用。github

Service workers

service worker是瀏覽器和服務器之間的腳本,主要做用是攔截請求,修改響應,以及一些其餘的做用。chrome

網站能夠正常工做的前提是能獲取到html,css,js等資源。在以前這些資源主要由瀏覽器管理,對於開發者而言是不可見的。如今經過service worker咱們能夠掌控這些資源。固然最終仍是經過瀏覽器控制他們的。json

掌握service worker的前提是掌握promisesegmentfault

Promise

Promise是用於處理異步操做的很好的方式,對於掌握service worker是相當重要的。api

Promise功能很強大,咱們不在這裏細述了。咱們只須要知道調用then()方法處理成功,catch方法處理錯誤就能夠了。promise

一個簡單的比較同步和異步操做的代碼

sync
    try {
        var value = Fn();
        console.log(value);
    } catch(err) {
        console.log(err);
    }
    
async
    Fn()
    .then(function(value) {
        console.log(value);
    })
    .catch(function(err) {
        console.log(err);
    });

service worker準備工做

Service workers可以運行的前提是網站採用了https。這是出於安全因素的考慮。

如今主流瀏覽器都已經支持service worker,不須要去單獨開啓了。

雖然service worker必定要在https的域名下面運行,可是本地的http://localhost域名卻不影響,能夠正常運行。

註冊service worker

一個service worker若是要生效,必需要先註冊。這個註冊的過程是發生在service worker以外的。通常會在index.html中。你能夠寫在js文件裏面,在html文件中引入,但不能在service worker的js中註冊。

如何註冊

  1. 先建立一個html文件
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
    <p>Registration status: <strong id="status"></strong></p>
    <script>
    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register(
            'service-worker.js',
            { scope: './' }
        ).then(function(serviceWorker) {
            document.getElementById('status').innerHTML = 'successful';
        }).catch(function(error) {
            document.getElementById('status').innerHTML = error;
        });
    } else {
        document.getElementById('status').innerHTML = 'unavailable';
    }
    </script>
</body>
</html>
  1. 在當前文件夾下面建立一個==名字叫service-worker.js==的js文件
  2. 啓動一個本地服務器,推薦使用anywhere,自帶了https

成功圖示

![image](http://wx2.sinaimg.cn/mw690/0...
)

程序如何運行

首先判斷瀏覽器支持狀況,若是不支持則作出提示。

咱們使用了空js文件註冊了service worker。register的第二個參數的scope表示此service worker的做用範圍是當前域名下面的根目錄。

如圖顯示:註冊成功。說明咱們的瀏覽器是支持service worker的。

卸載service worker

經過調用unregister()方法卸載service worker

serviceWorker.unregister().then(function() {
    document.getElementById('status').innerHTML = 'unregistered';
})

註冊service worker的詳細信息

瞭解service worker註冊過程當中的詳細信息和事件有助於咱們更好的掌控它。

註冊詳情

  1. 咱們建立一個以下的html頁面
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Detailed Registration</title>
</head>
<body>
  <p>Registration status: <strong id="status"></strong></p>
  <p>State: <strong id="state"></strong></p>

  <script>
    function printState(state) {
      document.getElementById('state').innerHTML = state;
    }

    if ('serviceWorker' in navigator) {

      navigator.serviceWorker.register(
        'service-worker.js',
        { scope: './' }
      ).then( function(registration) {
        var serviceWorker;

        document.getElementById('status').innerHTML = 'successful';

        if (registration.installing) {
          serviceWorker = registration.installing;
          printState('installing');
        } else if (registration.waiting) {
          serviceWorker = registration.waiting;
          printState('waiting');
        } else if (registration.active) {
          serviceWorker = registration.active;
          printState('active');
        }

        if (serviceWorker) {
          printState(serviceWorker.state);

          serviceWorker.addEventListener('statechange', function(e) {
            printState(e.target.state);
          });
        }
      }).catch(function(error) {
        document.getElementById('status').innerHTML = error;
      });
    } else {
        document.getElementById('status').innerHTML = 'unavailable';
      }
  </script>
</body>
</html>
  1. 建立一個service-worker.js文件
self.addEventListener('install', function(e) {
  console.log('Install Event:', e);
});

self.addEventListener('activate', function(e) {
  console.log('Activate Event:', e);
});
  1. 而後頁面以下顯示

image

程序如何運行

上面的代碼描述了service worker的3種狀態。當程序處於active狀態的時候,咱們就能夠刷新頁面查看處於service worker控制之下的頁面了。

在service worker中咱們註冊了兩個事件,installactivate,當service worker第一次註冊的時候會被觸發。

install事件比較適合用來預加載數據和初始化緩存,activate事件適合用來清理舊版本的數據。

其餘

當一個service worker被成功註冊,它會經歷如下狀態

Install

在service worker的生命週期中,若是service worker已經註冊沒有錯誤,可是還沒有激活。那以前已經激活的service worker就會仍然會控制着頁面。從新加載以後的service worker若是發生任何更改,就會從新安裝service worker。在安裝完成,激活以前,它不會攔截任何請求。

Activate

當service worker被激活時,它的狀態就是activate。service worker就能夠攔截請求了。只有當咱們關閉網頁從新打開,或者強制刷新當前頁面,纔會被激活。通常安裝成功以後不會當即處於activate狀態。

Fetch

在當前scope做用域下面的請求會觸發fetch事件

Terminate

這個事件可能會發生在任什麼時候候,主要後果就是須要瀏覽器作service worker的內存回收。以後根據須要重啓,但不是不會在觸發activate事件。

service worker將會始終攔截請求,重啓頁面也是爲了這個。雖然這麼說,但咱們沒法保證service worker任什麼時候候都處於生效狀態,因此在service worker中定義的全局狀態可能不會被保留。因此咱們最好使用indexDB和localStorage來實現持久化。

調試

service worker在瀏覽器中單獨線程運行,經過單獨的方式和頁面通訊。可是和頁面是處於不一樣的做用域。這就意味着service worker沒法訪問網頁的dom等其餘信息。所以咱們也沒法經過
DevTools裏面同一個tab來調試service worker。咱們須要一個單獨的Tab來調試service worker線程。

在service worker中,它大部分的工做是在監聽的事件中來完成的,好比在install事件中完成資源緩存。一樣咱們能夠在這裏打斷點。

怎麼作

下面來展現如何調試

  1. 在chrome中打開:chrome://inspect/#service-workers

image

  1. 或者在chrome中輸入:chrome://serviceworker-internals/ 若是列表裏面沒有的話,說明沒有service worker正在運行

image

  1. 在DevTools中的Source下面的service worker能夠看到對應的js腳本
  2. 在這裏能夠調試

image

一樣可使用console.log

  1. chrome://serviceworker-internals/頁面中,能夠看到每一個service worker下面有幾個按鈕。
  • Terminated:註銷service worker
  • Start/Stop: 開啓/中止service worker
  • Sync: 給worker同步Sync事件
  • push: 給worker同步push事件
  • Inspect:在檢查器中打開worker

==即便勾選了Network中的disable cache,service worker依然會生效,若是須要每次都更新,須要勾選Application->service worker->offline==

發生錯誤時提供穩定版本

  1. 建立一個html文件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Stale on Error</title>
</head>
<body>
  <p>Registration status: <strong id="status"></strong></p>
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register(
        'service-worker.js',
        { scope: './' }
      ).then( function(serviceWorker) {
        document.getElementById('status').innerHTML = 'successful';
      }).catch(function(error) {
        document.getElementById('status').innerHTML = error;
      });

    } else {
      document.getElementById('status').innerHTML = 'unavailable';
    }
  </script>
</body>
</html>
  1. 建立service-worker.js文件
var version = 1;
var cacheName = 'stale-' + version;

self.addEventListener('install', function(event) {
    self.skipWaiting();
});

self.addEventListener('activate', function(event) {
    if (self.clients && clients.claim) {
        clients.claim();
    }
});

self.addEventListener('fetch', function(event) {
    // Always fetch response from the network
    event.respondWith(
        fetch(event.request).then(function(response) {
            return caches.open(cacheName).then(function(cache) {
                // If we received an error response
                if(response.status >= 500) {
                    return cache.match(event.request).then(function(response) {
                        // Return stale version from cache
                        return response;
                    }).catch(function() {
                        // No stale version in cache so return network response
                        return response;
                    });
                } else {
                    // Response was healthy so update cached version
                    cache.put(event.request, response.clone());
                    return response;
                }
            });
        })
    );
});

當網絡中斷以後,頁面依然能夠訪問。

建立mock響應

咱們能夠模擬服務器,對客戶端進行響應。

  1. 建立index.html頁面
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Detailed Registration</title>
</head>
<body>
  <p>Network status: <strong id="status"></strong></p>
  <div id="request" style="display: none">
    <input id="long-url" value="https://www.packtpub.com/" size="50">
      <input type="button" id="url-shorten-btn" value="Shorten URL" />
    </div>
    <div>
      <input type="checkbox" id="mock-checkbox" checked>Mock Response</input>
    </div>
    <div>
      <br />
      <a href="" id="short-url"></a>
    </div>
  </div>

  <script>
    function printStatus(status) {
      document.getElementById('status').innerHTML = status;
    }

    function showRequest() {
      document.getElementById('url-shorten-btn').addEventListener('click', sendRequest);
      document.getElementById('request').style.display = 'block';
    }

    function sendRequest() {
      var xhr = new XMLHttpRequest(),
        request;

      xhr.open('POST',
        'https://www.googleapis.com/urlshortener/v1/url?key=AIzaSyCr0XVB-Hz1ohPpjvLatdj4qZ5zcSohHsU');
      xhr.setRequestHeader('Content-Type', 'application/json');

      if (document.getElementById('mock-checkbox').checked) {
        xhr.setRequestHeader('X-Mock-Response', 'yes');
      }

      xhr.addEventListener('load', function() {
        var response = JSON.parse(xhr.response);
        var el = document.getElementById('short-url');

        el.href = response.id;
        el.innerHTML = response.id;
      });

      request = {
        longUrl: document.getElementById('long-url').value
      };

      xhr.send(JSON.stringify(request));
    }

    if ('serviceWorker' in navigator) {

      navigator.serviceWorker.register(
        'service-worker.js',
        { scope: './' }
      ).then( function(registration) {
        if (navigator.serviceWorker.controller) {
            printStatus('The service worker is currently handling network operations.');
            showRequest();
          } else {
            printStatus('Please reload this page to allow the service worker to handle network operations.');
          }
      }).catch(function(error) {
        document.getElementById('status').innerHTML = error;
      });
    } else {
        document.getElementById('status').innerHTML = 'unavailable';
      }
  </script>
</body>
</html>
  1. 建立service-worker.js
self.addEventListener('fetch', function(event) {
  var requestUrl = new URL(event.request.url);

  if (requestUrl.pathname === '/urlshortener/v1/url' &&
      event.request.headers.has('X-Mock-Response')) {

    var response = {
      body: {
        kind: 'urlshortener#url',
        id: 'https://goo.gl/KqR3lJ',
        longUrl: 'https://www.packtpub.com/books/info/packt/about'
      },
      init: {
        status: 200,
        statusText: 'OK',
        headers: {
          'Content-Type': 'application/json',
          'X-Mock-Response': 'yes'
        }
      }
    };

    var mockResponse = new Response(JSON.stringify(response.body), response.init);

    console.log('Mock Response: ', response.body);
    event.respondWith(mockResponse);
  }
});

能夠看到頁面顯示的是service worker裏面咱們配置的響應

處理請求超時

請求超時有多是網絡鏈接的問題,service worker是解決這類問題的理想方案。

  1. 建立html文件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Request Timeouts</title>
</head>
<body>
  <p>Registration status: <strong id="status"></strong></p>

  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register(
        'service-worker.js',
        { scope: './' }
      ).then(function(serviceWorker) {
        document.getElementById('status').innerHTML = 'successful';
      })
    } else {
      document.getElementById('status').innerHTML = 'unavailable';
    }
  </script>

  <script src="https://code.jquery.com/jquery-2.2.0.js"></script>
</body>
</html>
  1. 建立service-worker.js
function timeout(delay) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(new Response('', {
                status: 408,
                statusText: 'Request timed out.'
            }));
        }, delay);
    });
}

self.addEventListener('install', function(event) {
    self.skipWaiting();
});

self.addEventListener('activate', function(event) {
    if (self.clients && clients.claim) {
        clients.claim();
    }
});

self.addEventListener('fetch', function(event) {
  if (/\.js$/.test(event.request.url)) {
    event.respondWith(Promise.race([timeout(400), fetch(event.request.url)]));
  } else {
    event.respondWith(fetch(event.request));
  }
});

當咱們把jquery地址換成一個錯誤的地址,咱們看到一個408的響應。

關注個人微信公衆號,更多優質文章定時推送
clipboard.png

相關文章
相關標籤/搜索