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>
成功图示

首先判断浏览器支持状况,若是不支持则作出提示。
咱们使用了空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的响应。
关注个人微信公众号,更多优质文章定时推送