designer: heyunjiang time: 2018.6.11 update: 2018.6.19
目录
学习目标(要解决什么问题):为了实现pwa
mdn: Web Workers 使得一个Web应用程序可以在与主执行线程分离的后台线程中运行一个脚本操作。这样做的好处是可以在一个单独的线程中执行费时的处理任务,从而允许主(通常是UI)线程运行而不被阻塞/放慢。
web workers 线程与主线程之间通信,是通过 postMessage()
方法发送消息,通过 onmessage
这个 event handler 来接收消息。数据消息传递的是副本,不是共享数据(通过消息通道传递的)
web workers 线程中,不能直接操作dom(只能在主线程中操作dom),也不能使用 window 对象中的一些默认的方法和属性,比如:??。
worker线程中,能够使用大部分window对象提供的方法和属性,包括 websockets、indexedDB等
通用
主线程
worker 线程
// shared worker 线程: 根据端口来进行通信
onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}
}
// dedicated worker 线程: 直接通信
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}
// 主线程
var myWorker = new Worker("worker.js");
myWorker.postMessage('hello world');
myWorker.onmessage = function(e) {
console.log(e.data);
}
注意:
if (window.Worker)
service worker 作为pwa应用最重要的一环。
实现功能:充当web应用程序和浏览器之间的代理,也可以充当浏览器和网络之间的代理。旨在创建离线应用,拦截网络请求,网络可用时更新本地资源。附加功能有消息推送、设备后台同步api
单个 service worker 可以控制很多页面。每个你的 scope 里的页面加载完的时候,安装在页面的 service worker 可以控制它。牢记你需要小心 service worker 脚本里的全局变量: 每个页面不会有自己独有的worker
service worker
特性
https
环境下( localhost
或者 127.0.0.1
也是 ok 的)完全异步
,依赖 promise
,不可以使用 localstorage
与 xhr
,可以使用 indexDB
和 fetch
cache api
实现fetch
(fetch在主流浏览器中都已经实现了,可以替代xhr)1 注册或获取
navigator.serviceWorker.register('service-worker.js', {scope: './'}).then(function(reg){})
如果注册成功,则返回一个 promise
。然后注册的 service worker
线程独立运行。
scope表示 service worker
要控制的子目录,路径相对于 origin
,不是当前js文件, service-worker.js
也一样
2 安装
运行注册service worker的主线程所在页面开始安装service worker。开始安装到安装成功,会触发事件:install
。
可以在 worker线程
里面监听事件,并 处理 indexDB 和 缓存站点资源了
3 激活
当 service worker 安装完成后,会接收到一个激活事件: activate
。
onactivate 主要用途是 清理先前版本的service worker 脚本中使用的资源。
4 重新加载页面
重新加载页面,保证 service worker
能完全控制页面
主线程支持状态判断
service worker 支持事件列表
6个阶段
register -> installing -> installed -> activating -> activated -> redundant
register
: 在主线程中进行注册installing
:安装时会触发 install
事件,该事件包含2个方法:event.waitUntil
和 self.skipWaiting
。 self.skipWaiting
用于跳过 waiting 状态,直接进入 activating
installed
: 安装成功后,waiting, 等待其他的service worker线程关闭,然后它才能进入激活activating
: 激活中,当其他的service worker线程(控制该客户端的)关闭后,允许当前service worker的安装、激活。激活中会触发 activate
事件,该事件包含2个方法:event.waitUntil
和 self.clients.claim
。 self.clients.claim
用于取得页面的控制权,其他的service worker就强制进入 redundant
activated
: 激活后,然后处理 activate
的回掉事件,例如 fetch
、 sync
、 push
redundant
: 结束app.js // 主线程
// 代码1:主线程获取或注册 service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/sw-test/' }).then(function(reg) {
if(reg.installing) {
console.log('Service worker installing');
} else if(reg.waiting) {
console.log('Service worker installed');
} else if(reg.active) {
console.log('Service worker active');
}
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
sw.js // service worker 线程
cache
作为 service worker 作用域的一个全局变量
caches
作为 service worker 作用域的一个全局变量,原名 CacheStorage
promise.catch
就是在没有联网的时候触发,可以自定义返回数据
// 代码2:service worker 缓存文件
self.addEventListener('install', function(event) {
// waitUntil 保证缓存数据成功前,service worker 不会 install 完成
event.waitUntil(
// v1 表示当前 service worker 激活使用的版本
caches.open('v1').then(function(cache) {
return cache.addAll([
'/sw-test/',
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/image-list.js',
'/sw-test/star-wars-logo.jpg',
'/sw-test/gallery/bountyHunters.jpg',
'/sw-test/gallery/myLittleVader.jpg',
'/sw-test/gallery/snowTroopers.jpg'
]);
})
);
});
// 代码3:service worker 拦截请求
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request).then(function(response) {
// service worker 保证 caches.match() 总是会 resolves
// 但是返回 response 可能为 undefined
if (response !== undefined) {
return response;
} else {
return fetch(event.request).then(function (response) {
// response 只会被使用一次
// 保存 clone 版本到 cache 中
// 然后返回 response
let responseClone = response.clone();
caches.open('v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function () {
// 如果网络错误,则返回默认配置
return caches.match('/sw-test/gallery/myLittleVader.jpg');
});
}
}));
});
问题:上面例子实现了数据缓存、拦截请求,以及处理断网状态如何返回数据,那么如何更新缓存中的数据呢?
答:每次都让其执行fetch,失败再读取缓存。或许上面处理例子可以改写为
// 代码4:service worker 能更新缓存的拦截请求
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request).then(function(response) {
let responsePre = response&&response.clone()||null;
return fetch(event.request).then(function (response) {
let responseClone = response.clone();
caches.open('v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function () {
if (responsePre !== null) {
return responsePre
} else {
return caches.match('/sw-test/gallery/myLittleVader.jpg');
}
});
}));
});
先安装:如果有旧版的 worker 已经被安装,那么在刷新页面的时候,新版本的 worker 虽然会被安装,但是不会被激活。
后激活:当没有任何已加载的页面在使用旧版的 worker 的时候,新版本才会被激活
再清理:当新的service worker激活之后,需要清理之前版本缓存
// 代码5:service worker 更新激活时清理缓存
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);
}
}));
})
);
});
https
环境下运行项目origin
,scope也一样,但是要求必须在应用目录之下问:为什么不可以使用localstorage?为什么它就是同步的?为什么就可以使用indexDB?这几种存储方式之间有什么异同?
问:为什么不能使用xhr,能够使用fetch?这2者有什么区别?
答:xhr采用的是传统回调函数写法,虽然是异步请求,但是是同步操作与响应。fetch返回的是promise,也是异步请求,但是是异步操作与响应。axois中使用的就是promise搭起基于xhr的异步桥梁。
pwa、数据mock、网速很差或者离线情况是保证良好用户体验
chrome -> Application -> Service Workers
参考文章:
service worker && cachestorage
练习:
练习应用场景
做一个pwa应用,就在我的手写签章里面实现