【PWA】Service Worker 全面进阶_pwa servicewwork(2)

'static/pre\_fetched.txt',
'static/pre\_fetched.html',
'<https://www.chromium.org/_/rsrc/1302286216006/config/customLogo.gif>'

];

event.waitUntil(
caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
// 使用 url 对象进行路由拼接
var url = new URL(urlToPrefetch, location.href);
url.search += (url.search ? ‘&’ : ‘?’) + ‘cache-bust=’ + now;
// 创建 request 对象进行流量的获取
var request = new Request(url, {mode: ‘no-cors’});
// 手动发送请求,用来进行文件的更新
return fetch(request).then(function(response) {
if (response.status >= 400) {
// 解决请求失败时的情况
throw new Error('request for ’ + urlToPrefetch +
’ failed with status ’ + response.statusText);
}
// 将成功后的 response 流,存放在 caches 套件中,完成指定文件的更新。
return cache.put(urlToPrefetch, response);
}).catch(function(error) {
console.error('Not caching ’ + urlToPrefetch + ’ due to ’ + error);
});
});

  return Promise.all(cachePromises).then(function() {
    console.log('Pre-fetching complete.');
  });
}).catch(function(error) {
  console.error('Pre-fetching failed:', error);
})

);
});


当成功获取到缓存之后, SW 并不会直接进行替换,他会等到用户下一次刷新页面过后,使用新的缓存文件。


![fileUpload](http://static.zybuluo.com/jimmythr/i4lcpbg7dbclzntzoo0kxeqr/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202016-12-18%2023.56.44.png)


不过,这里请注意,我并没有说,我们更新缓存只能在 `install` 里更新,事实上,更新缓存可以在任何地方执行。它主要的目的是用来更新 caches 里面缓存套件。我们提取一下代码:



// 找到缓存套件并打开
caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
// 根据事先定义的路由开始发送请求
var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
// 执行 fetch
return fetch(request).then(function(response) {
// 缓存请求到的资源
return cache.put(urlToPrefetch, response);
}).catch(function(error) {
console.error('Not caching ’ + urlToPrefetch + ’ due to ’ + error);
});
});
// 使用 promise.all 进行全部捕获
return Promise.all(cachePromises).then(function() {
console.log(‘Pre-fetching complete.’);
});
}).catch(function(error) {
console.error(‘Pre-fetching failed:’, error);
})


现在,我们已经拿到了核心代码,那有没有什么简便的办法,让我们少写一些配置项,直接对每一个文件进行文件更新教研。 有的!!! 还记得上面的 `fetch` 事件吗?我们简单回顾一下它的代码:



self.addEventListener(‘fetch’, function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});


实际上,我们可以将上面的核心代码做一些变化直接用上:



self.addEventListener(‘fetch’, function(event) {
event.respondWith(
caches.open(‘mysite-dynamic’).then(function(cache) {
return cache.match(event.request).then(function(response) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
return response || fetchPromise;
})
})
);
});


这里比较难的地方在于,我们并没有去捕获 fetch(fetchRequest)… 相关内容。也就是说,这一块是完全独立于我们的主体业务的。他的 fetch 只是用更新文件而已。我们可以使用一个流图进行表示:


![prefetch](http://static.zybuluo.com/jimmythr/ccxpjfaquu9txxjbluxb5cja/flowchar.png)


#### 用户更新


现在,为了更好的用户体验,我们可以做的更尊重用户一些。可以设置一个 button,告诉用户是否选择缓存指定文件。有同学可能会想到使用 postmessage API,来告诉 SW 执行相关的缓存信息。不过事实上,还有更简单的办法来完成,即,直接使用 caches 对象。caches 和 web worker 类似。都是直接挂载到 window 对象上的。所以,我们可以直接使用 caches 这个全局变量来进行搜索。那么该环节就不需要直接通过 SW,这个流程图可以画为:


![SW_](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/images/cm-on-user-interaction.png)


代码可以参考:



document.querySelector(‘.cache-article’).addEventListener(‘click’, function(event) {
event.preventDefault();

var id = this.dataset.articleId;
// 创建 caches 套件
caches.open(‘mysite-article-’ + id).then(function(cache) {
fetch(‘/get-article-urls?id=’ + id).then(function(response) {
// 返回 json 对象
return response.json();
}).then(function(data) {
// 缓存指定路由
cache.addAll(data);
});
});
});


不过上述的弊端也是有的,你把缓存的更新交给前台来做,实际上是增加的 js 主线程的负担,所以,按照一般的思路,我们是需要通过 `message` 来触发 service worker 的文件更新。


### Message 通信


这里,我们简单介绍一下, client 和 service worker 相互通信。首先, service worker 向 client 发送消息很容易。



self.addEventListener(‘message’, function(event){
console.log("SW Received Message: " + event.data);
event.ports[0].postMessage(“SW Says ‘Hello back!’”);
});


其中 event.ports 是根据 `messageChannel` API 构造出来的。该 API 的用法大家 MDN 一下吧。我们主要来看一下,如何在 clients 端,向 service worker 传递信息。这里就要借由:`ServiceWorkerContainer`这个对象在 navigator 上获取。



navigator.serviceWorker === ServiceWorkerContainer


在该 API 上挂载了一些方法和属性,用来获取 service worker 上的相关状态。来看一下:


![image_1bahvclk520dent1p37i1g111ng.png-34.2kB](http://static.zybuluo.com/jimmythr/yl06lin9ulou3vessy1d5pm6/image_1bahvclk520dent1p37i1g111ng.png)


其中和通信相关的就两个,一个是 `controller`,一个是 `onmessage` 事件。


* controller: 通过其上挂载的 postMessage API 向 Service worker 发送信息。
* onmessage: 接受 Service worker 发送的消息


即:



function send_message_to_sw(msg){
navigator.serviceWorker.controller.postMessage(“Client 1 says '”+msg+“'”);
}


而 Service Worker 只要负责接收即可:



self.addEventListener(‘message’,event =>{
console.log(“receive message” + event.data);
});


该方法常常用于,用来更新根目录的 `html` 文档,因为 SW 在更新的时候,并不会对根目录下的 `HTML`文档进行更新。那么综上所述,一般而言,除了根文件而言,其他文件只要根据 hash 值来跟新即可。我们看一下整个的写法:



self.addEventListener(‘fetch’, function(event) {
event.respondWith(
caches.match(event.request).then(function(resp) {
fetchList.push(event.request.url);
return resp || fetch(event.request).then(function(response) {
return caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
console.log(‘update files like’ + event.request.url);
cache.put(event.request, response.clone());
return response;
});
});
})
);
});

self.addEventListener(‘message’,event =>{
console.log(“receive message” + event.data);
// 更新根目录下的 html 文件。
var url = event.data;
console.log("update root file " + url);
event.waitUntil(
caches.open(CURRENT_CACHES.prefetch).then(cache=>{
return fetch(url)
.then(res=>{
cache.put(url,res);
})
})
)
});


然后,在 index.js 中,添加 postMessage API。记住,每次网页打开时,并不会每次都触发 `register`,因为你已经接受 SW,所以,我们需要做额外的判断,来检测是否发送相关的消息。那么我们在代码中就可以添加如下内容:



var SW = navigator.serviceWorker;
SW.register(‘sw.js’).then(function(registration) {
//…
}).catch(function(err) {
});
if(SW.controller){
console.log(‘send message ::’);
SW.controller.postMessage(location.href)
}


如果想更深入了解 `Message` 的相关处理,可以参考:[sendMessage](https://bbs.csdn.net/topics/618166371)


### Sync 离线处理


Sync 是为了更好解决没网的条件下,一些请求发送成功性问题。该场景的处理常常针对即时性要求比较高的业务。比如:IM,弱网数据存储,朋友圈动态发送等。在以前,我们针对没网的条件是通过 `navigator.onLine` 来判断有没有网。不过,这种方式会比较生硬,在没网的条件下也无法正常发送请求。而 Sync 则可以帮助我们完整这件事,假如用户在发朋友圈,而此时又没网了。。。通过 Sync,用户可以正常的发送该朋友圈(此时,并未真正的发送请求),当网重新连接,会触发 `Service Worker`中的 `sync` 事件。


直接看个例子:



// 通过 reg.sync 注册一个 Sync
SW.ready.then(reg=>{
return reg.sync.register(‘yes’);
})
.then(res=>{
console.log(“成功”+ res);
})

// sw.js
self.addEventListener(‘sync’,event=>{
// 监听 Sync 事件
if(event.tag === ‘yes’)fetch(‘/’);
})


需要说明的是,`sync` 触发条件时:有网!有网!有网!


即,当在没网条件时,该 `sync` 事件是不会被触发的。提醒大家一点,在测试的时候,最好是直接关闭 `wifi` 或者直接断网,使用 Chrome 浏览器内置的 `offline` 好像没有用。


另外,如果遇到用户重复发送时,需要注意使用不同的 `tag` 进行注册。因为,多个相同的 `Sync` 触发的时候,会自动进行合并,使用最新的。



// 没网条件下,连续注册 3 次
reg.sync.register(‘yes’);
reg.sync.register(‘yes’);
reg.sync.register(‘yes’);
// 只有第三次会成功


### Caches 相关


上面大致了解了一下关于 SW 的基本流程,不过说到底,SW 只是一个容器,它的内涵只是一个驻留后台进程。我们想关心的是,在这进程里面,我们可以做些什么? 最主要的应该有两个东西,缓存和推送。这里我们主要讲解一下缓存。不过在SW 中,我们一般只能缓存 `POST` 上面在文件更新里面也讲了几个更新的方式。简单来说:


![desk](http://static.zybuluo.com/jimmythr/soso8r7o9gxc9blfcojvmzsn/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-01-02%2023.31.21.png)


简单的情形上面已经说了,我这里专门将一下比较复杂的内容。


#### 网络缓存同时干


这种情形一般是用来装逼的,一方面检查请求,一方面有检查缓存,然后看两个谁快,就用谁,我这里直接上代码吧:



function promiseAny(promises) {
return new Promise((resolve, reject) => {
// 通过 promise 的 resolve 特性来决定谁快
promises = promises.map(p => Promise.resolve§);
// 这里调用外层的 resolve
promises.forEach(p => p.then(resolve));
// 如果其中有一方出现 error,则直接挂掉
promises.reduce((a, b) => a.catch(() => b))
.catch(() => reject(Error(“All failed”)));
});
};

self.addEventListener(‘fetch’, function(event) {
event.respondWith(
promiseAny([
caches.match(event.request),
fetch(event.request)
])
);
});


#### 总是更新


这里就和我们在后台配置的 Last-Modifier || Etag 一样,询问更新的文件内容,然后执行更新:



self.addEventListener(‘fetch’, function(event) {
event.respondWith(
caches.open(‘mysite-dynamic’).then(function(cache) {
return fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
});
})
);
});


#### 先返回后更新


这应该是目前为止最佳的体验,返回的时候不会影响正在发送的请求,而接受到的新的请求后,最新的文件会替换旧的文件。(这个就是前面写的代码):



self.addEventListener(‘fetch’, function(event) {
event.respondWith(
caches.open(‘mysite-dynamic’).then(function(cache) {
return cache.match(event.request).then(function(response) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
return response || fetchPromise;
})
})
);
});


#### 最佳实践更新


上面说的哪几种都有某一方面的缺陷,那如果才能实现一种比较靠谱的缓存方案呢?我们借鉴一下上面提到的 `message` 通信方式,方案为:


![cache.svg-5.7kB](http://static.zybuluo.com/jimmythr/mnnji7skx0axb5sq8l9dax3i/cache.svg)


我们对资源做的主要工作就是如果处理好 `fetch` 事件中的相关资源。这里,我们简单的通过文件类型这个点来进行划分,优先情况是缓存 `js/css` 文件。那在 fetch 事件中,我们就需要写为:



var CURRENT_CACHES = {
prefetch: ‘prefetch-cache-v’ + 1;
};
var FILE_LISTS = [‘js’,‘css’,‘png’];

var goSaving = function(url){
for(var file of FILE_LISTS){
if(url.endsWith(file)) return true;
}
return false;
}

self.addEventListener(‘fetch’, function(event) {
event.respondWith(
caches.match(event.request).then(function(resp) {
return resp || fetch(event.request).then(function(response) {
// 检查是否需要缓存
var url = event.request.url;
if(!goSaving(url))return response;
console.log(‘save file:’ + url);
// 需要缓存,则将资源放到 caches Object 中
return caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
console.log(‘update files like’ + event.request.url);
cache.put(event.request, response.clone());
return response;
});
});
})
);
});


这样通过在 `FILE_LISTS` 添加想要缓存的文件类型即可。之后,我们只要在 [message](https://bbs.csdn.net/topics/618166371) 中更新原来的 `document` 即可。



self.addEventListener(‘message’,event =>{
console.log(“receive message” + event.data);
// 更新根目录下的 html 文件。
var url = event.data;
console.log("update root file " + url);
event.waitUntil(
caches.open(CURRENT_CACHES.prefetch).then(cache=>{
return fetch(url)
.then(res=>{
cache.put(url,res);
})
})
)
});


接下来,我们来详细了解一下关于 Cache Object 相关的内容。加深印象:


### Cache Object


Cache 虽然是在 SW 中定义的,但是我们也可以直接在 window 域下面直接使用它。它通过 Request/Response 流(就是 fetch)来进行内容的缓存。每个域名可以有多个 Cache Object,具体我们可以在控制台中查看:


![cacheObject](http://static.zybuluo.com/jimmythr/2q4e4bpyxmwoqq8ik591qt1j/cache.png)


并且 Cache Object 是懒更新,实际上,就可以把它比喻为一个文件夹。如果你不自己亲自更新,系统是不会帮你做任何事情的。对于删除也是一样的道理,如果你不显示删除,它会一直存在的。不过,浏览器对于每个域名的 Cache Object 数量是有限制的,并且,会周期性的删掉一些缓存信息。最好的办法,是我们自己管理资源,官方给出的建议是: 使用版本号进行资源管理。上面我也展示过,删除特定版本的缓存资源:



self.addEventListener(‘activate’, function(event) {
var cacheWhitelist = [‘v2’];

event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (cacheWhitelist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
);
});


#### Cache Object 操作相关方法


这里,我们就可以将 Cache Object 理解为一个持久性数据库,那么针对于数据库来说,简单的操作就是 CRUD。而 Cache Object 也提供了这几个接口,并且接口结果都是通过 Promise 对象返回的,成功返回对应结果,失败则返回 undefined:


* Cache.match(request, options): 成功时,返回对应的响应流–response。当然,查找的时候使用的是正则匹配,表示是否含有某个具体字段。
	+ options:
		- ignoreSearch[boolean]:是否忽略 querystring 的查找。即,我们查找的区域不包括 qs。比如: `<http://foo.com/?value=bar>`,我们不会再搜索 `?value=bar` 这几个字符。
		- ignoreMethod[boolean]:当设置为 true 时,会防止 Cache 验证 http method,默认情况下,只有 GET 和 HEAD 能够通过。默认值为 false。
		- ignoreVary[boolean]:当设置为 true 时,表示不对 vary 响应头做验证。即, Cache 只需要通过 URL 做匹配即可,不需要对响应头 vary 做验证。默认值为 false。
		- cacheName[String]: 自己设置的缓存名字。一般用不到,match 会自动忽略。



cache.match(request,{options}).then(function(response) {
//do something with the response
});


* Cache.matchAll(request, options): 成功时,返回一个数组,包含所有匹配到的响应流。options 和上面的一样,这里就不多说了。



cache.matchAll(request,{options}).then(function(response) {
response.forEach(function(element, index, array) {
cache.delete(element);
});
});


* Cache.add(url): 这实际上就是一个语法糖。fetch + put。即,它会自动的向路由发起请求,然后缓存获取到的内容。



cache.add(url).then(function() {
// 请求的资源被成功缓存
});

等同于

fetch(url).then(function (response) {
if (!response.ok) {
throw new TypeError(‘bad response status’);
}
return cache.put(url, response);
})
.then(res=>{
// 成功缓存
})


* Cache.addAll(requests):这个就是上面 cache.add 的 Promise.all 实现方式。接受一个 Urls 数组,然后发送请求,缓存上面所有的资源。



this.addEventListener(‘install’, function(event) {
event.waitUntil(
caches.open(‘v1’).then(function(cache) {
return cache.addAll([
‘/public/’,
‘/public/index.html’,
‘/public/style.css’,
‘/public/app.js’
]);
})
);
});


* Cache.put(request, response): 将请求的资源以 req/res 键值对的形式进行缓存。如果,之前已经存在对应的 req(即,key 值),那么以前的值将会被新值覆盖。



cache.put(request, response).then(function() {
// 成功缓存
});


* Cache.delete(request, options): 用来删除指定的 cache。如果你不删除,该资源会永远存在(除非电脑自动清理)。
* Cache.keys(request, options): 返回当前缓存资源的所有 key 值。



cache.keys().then(function(keys) {
keys.forEach(function(request, index, array) {
  cache.delete(request);
});

});


可以查看到上面的参数都共同的用到了 `request` 这就是 fetch 套件里面的请求流,具体,可以参考一下前面的代码。上面所有方法都是返回一个 Promise 对象,用来进行异步操作。


上面简单介绍了一下 Cache Object,但实际上,Cache 的管理方式是两级管理。即,最外层是 `Cache Storage`,下一层是 `Cache Object`。


#### Cache Storage


浏览器会给每个域名预留一个 Cache Storage(只有一个)。然后,剩下的缓存资源,全部都存在下面。我们可以理解为,这就是一个顶级缓存目录管理。而我们获取 Cache Object 的唯一途径,就是通过 caches.open() 进行获取。这里,我们就可以将 open 方法理解为 `没有已经存在的 Cache Object 则新建,否则直接打开`。它的相关操作方法也有很多:


* CacheStorage.match(request,{options}):在所有的 Cache Object 中进行缓存匹配。返回值为 Promise



caches.match(event.request).then(function(resp) {
return resp || fetch(event.request).then(function® {
caches.open(‘v1’).then(function(cache) {
cache.put(event.request, r);
});
return r.clone();
});
});


* CacheStorage.has(cacheName): 用来检查是否存在指定的 Cache Object。返回 Boolean 代表是否存在。



caches.has(‘v1’).then(function(hasCache) {
// 检测是否存在 Cache Object Name 为 v1 的缓存内容
if (!hasCache) {
// 没存在
} else {
//…
}
}).catch(function() {
// 处理异常
});


* CacheStorage.open(cacheName): 打开指定的 Cache Object。并返回 Cache Object。



caches.open(‘v1’).then(function(cache) {
cache.add(‘/index.html’);
});


* CacheStorage.delete(cacheName): 用来删除指定的 Cache Object,返回值为 Boolean:



caches.delete(cacheName).then(function(isDeleted) {
// 检测是否删除成功
});

通过,可以通过 Promise.all 的形式来删除多个 cache object

Promise.all(keyList.map(function(key) {
if (cacheList.indexOf(key) === -1) {
return caches.delete(keyList[i]);
}
});


* CacheStorage.keys(): 以数组的形式,返回当前 Cache Storage 保存的所有 Cache Object Name。



event.waitUntil(

最后

资料过多,篇幅有限

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。

acheStorage.delete(cacheName): 用来删除指定的 Cache Object,返回值为 Boolean:

caches.delete(cacheName).then(function(isDeleted) {
  // 检测是否删除成功
});

# 通过,可以通过 Promise.all 的形式来删除多个 cache object
Promise.all(keyList.map(function(key) {
        if (cacheList.indexOf(key) === -1) {
          return caches.delete(keyList[i]);
        }
      });

  • CacheStorage.keys(): 以数组的形式,返回当前 Cache Storage 保存的所有 Cache Object Name。
event.waitUntil(


### 最后

[外链图片转存中...(img-DKWHqUwJ-1714406969453)]

[外链图片转存中...(img-s8Xdz0F1-1714406969454)]

**资料过多,篇幅有限**

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

>自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值