pwa-之service worker 基本概念
pwa-之service worker 離線文件處理css
在本章,將涵蓋如下內容html
若是你是一個旅行愛好者,應該會常常陷入沒有網絡的狀況。這是使人沮喪的。特別是你有事情的時候。jquery
service worker是一個在==瀏覽器後臺==運行的腳本。不管網絡鏈接如何,可以使用Web應用程序意味着用戶能夠在飛機,地鐵或鏈接受限或不可用的地方不間斷地操做。 該技術將有助於提升客戶端的工做效率,並將提升應用程序的可用性。git
經過service worker,咱們能夠預先緩存網站的某些資源。 咱們做爲資源引用的是JavaScript文件,CSS文件,圖像和一些字體。 這將有助於咱們加快加載時間,而沒必要每次訪問同一網站時都必須從服務器獲取。 固然,最重要的是,當咱們網絡不順暢時,這些資源將可供咱們使用。github
service worker是瀏覽器和服務器之間的腳本,主要做用是攔截請求,修改響應,以及一些其餘的做用。chrome
網站能夠正常工做的前提是能獲取到html,css,js等資源。在以前這些資源主要由瀏覽器管理,對於開發者而言是不可見的。如今經過service worker咱們能夠掌控這些資源。固然最終仍是經過瀏覽器控制他們的。json
掌握service worker的前提是掌握promise
segmentfault
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 workers可以運行的前提是網站採用了https。這是出於安全因素的考慮。
如今主流瀏覽器都已經支持service worker,不須要去單獨開啓了。
雖然service worker必定要在https的域名下面運行,可是本地的http://localhost
域名卻不影響,能夠正常運行。
一個service worker若是要生效,必需要先註冊。這個註冊的過程是發生在service worker以外的。通常會在index.html
中。你能夠寫在js文件裏面,在html文件中引入,但不能在service worker的js中註冊。
<!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>
成功圖示
![image](http://wx2.sinaimg.cn/mw690/0...
)
首先判斷瀏覽器支持狀況,若是不支持則作出提示。
咱們使用了空js文件註冊了service worker。register的第二個參數的scope
表示此service worker的做用範圍是當前域名下面的根目錄。
如圖顯示:註冊成功。說明咱們的瀏覽器是支持service worker的。
經過調用unregister()
方法卸載service worker
serviceWorker.unregister().then(function() { document.getElementById('status').innerHTML = 'unregistered'; })
瞭解service worker註冊過程當中的詳細信息和事件有助於咱們更好的掌控它。
<!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>
self.addEventListener('install', function(e) { console.log('Install Event:', e); }); self.addEventListener('activate', function(e) { console.log('Activate Event:', e); });
上面的代碼描述了service worker的3種狀態。當程序處於active
狀態的時候,咱們就能夠刷新頁面查看處於service worker控制之下的頁面了。
在service worker中咱們註冊了兩個事件,install
和activate
,當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
事件中完成資源緩存。一樣咱們能夠在這裏打斷點。
下面來展現如何調試
chrome://inspect/#service-workers
chrome://serviceworker-internals/
若是列表裏面沒有的話,說明沒有service worker正在運行一樣可使用console.log
。
chrome://serviceworker-internals/
頁面中,能夠看到每一個service worker下面有幾個按鈕。==即便勾選了Network中的disable cache,service worker依然會生效,若是須要每次都更新,須要勾選Application->service worker->offline==
<!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>
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; } }); }) ); });
當網絡中斷以後,頁面依然能夠訪問。
咱們能夠模擬服務器,對客戶端進行響應。
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>
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是解決這類問題的理想方案。
<!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>
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的響應。
關注個人微信公衆號,更多優質文章定時推送