pwa-之service worker 基本概念
pwa-之service worker 离线文件处理
学习service worker 基本概念
在本章,将涵盖以下内容
- service worker准备工作
- 注册service worker
- 注册service worker细节
- 调试
- 出现错误时提供稳定版本
- 创建mock响应
- 处理请求超时
简介
如果你是一个旅行爱好者,应该会经常陷入没有网络的情况。这是令人沮丧的。特别是你有事情的时候。
service worker是一个在==浏览器后台==运行的脚本。无论网络连接如何,能够使用Web应用程序意味着用户可以在飞机,地铁或连接受限或不可用的地方不间断地操作。 该技术将有助于提高客户端的工作效率,并将提高应用程序的可用性。
通过service worker,我们可以预先缓存网站的某些资源。 我们作为资源引用的是JavaScript文件,CSS文件,图像和一些字体。 这将有助于我们加快加载时间,而不必每次访问同一网站时都必须从服务器获取。 当然,最重要的是,当我们网络不畅时,这些资源将可供我们使用。
Service workers
service worker是浏览器和服务器之间的脚本,主要作用是拦截请求,修改响应,以及一些其他的作用。
网站可以正常工作的前提是能获取到html,css,js等资源。在之前这些资源主要由浏览器管理,对于开发者而言是不可见的。现在通过service worker我们可以掌控这些资源。当然最终还是通过浏览器控制他们的。
掌握service worker的前提是掌握promise
Promise
Promise是用于处理异步操作的很好的方式,对于掌握service worker是至关重要的。
Promise功能很强大,我们不在这里细述了。我们只需要知道调用then()
方法处理成功,catch
方法处理错误就可以了。
一个简单的比较同步和异步操作的代码
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中注册。
如何注册
- 先创建一个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>
- 在当前文件夹下面创建一个==名字叫service-worker.js==的js文件
- 启动一个本地服务器,推荐使用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注册过程中的详细信息和事件有助于我们更好的掌控它。
注册详情
- 我们创建一个如下的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>
- 创建一个service-worker.js文件
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中打开:
chrome://inspect/#service-workers
- 或者在chrome中输入:
chrome://serviceworker-internals/
如果列表里面没有的话,说明没有service worker正在运行
- 在DevTools中的Source下面的service worker可以看到对应的js脚本
- 在这里可以调试
同样可以使用console.log
。
- 在
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==
发生错误时提供稳定版本
- 创建一个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>
- 创建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响应
我们可以模拟服务器,对客户端进行响应。
- 创建
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>
- 创建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是解决这类问题的理想方案。
- 创建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>
- 创建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的响应。
关注我的微信公众号,更多优质文章定时推送
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。