前言
当我们在一些地下停车场,或者在火车上、电梯等无法避免的信号不稳定的场所,使用网站应用处理一些表单操作或者上传数据的操作时,面临的将是网络连接错误的响应,使用户的操作白费。
而此刻 PWA 的 Sync API 就很好的解决了这个问题,让用户处理一些数据上传的操作时,无需关系网络环境,所有相关操作均会完成。Sync API 也是 PWA 离线里面的重要一环,下面就说一块。
SyncManager API
SyncManager 接口提供了用于注册和获取 Sync 注册的接口。Sync 是一个简单且非常实用的功能。
通过 ServiceWorkerRegistration 接口的 sync 进行获取:
navigator.serviceWorker.ready.then(reg => {console.log(reg.sync)
})
方法
SyncManager.register
用于注册一个 sync tag,tag 按照自己的需求设置。
语法:
SyncManager.register(DOMString tag).then(function(void) { ... })
返回 void 的 Promise。
SyncManager.getTags
用于获取已注册且未完成的 sync tag(完成后的 sync tag 会自动从此列表中删除)。
语法:
SyncManager.getTags().then(function(tags[]) { ... })
返回注册 syn tag 的字符串数组的 Promise。
事件
onsync
注册后的 Sync tag 会触发 ServiceWorkerGlobalScope 下的 onsync 事件。
此事件值包含两个属性:
tag
:返回触发此次事件的注册 Sync tag 的 tag 值。lastChance
:如果浏览器在尝试多次后还未成功,当 lastChance 为true
时表示不再尝试,且此次 sync tag 删除。
流程
从注册一个 Sync tag 到这个 Sync tag 完成,会经历三个阶段:
- Registered sync:注册 sync。
- Dispatched sync event:发出 sync 事件。
- Sync completed:Sync 完成。
SyncManager.register(tag)
后,会立即注册 sync,并将注册后的 sync tag 放入 sync 的注册列表中(可以通过SyncManager.getTags()
获取到),然后会判断当前的网络环境,只有网络在线的情况下,注册的 Sync 才会发出 sync 事件,然后当 SyncEvent.waitUntil()
中 Promise 为 reject 时将会周期性的触发 onsync 事件,直到不为 reject 才会完成 Sync tag,然后将相关 tag 清除。
在 Chrome 下,当 SyncEvent.waitUntil()
中的参数值一直为 Promise reject 时,会最多触发三次 onsync 事件,每次的周期时间至少为 5 分钟。
注意:sync 事件中的处理结果必须放在 SyncEvent.waitUntil() 中,否则会立即完成 Sync。
注意:上图中的重试次数和周期时间是 Chrome 环境下的体现,具体次数和周期标准中未规范,可以 e.lastChance 来判读,处理最后一次的相关逻辑。
可以通过 DevTools 里的 Background Services 查看 Sync 的执行过程:
使用场景
SyncManager 本身只是一个简单的 API,sync 事件中也只有两个只读属性,所以基于 Sync 来做的同步数据,比较好的方式搭配 IndexDB 来实现,下面两个场景也是基于 IndexDB。
1. 完全 Sync 化数据请求传输
这种场景下,相关场景的数据请求先写入 IndexDB 中,然后注册 Sync,在 onsync 中根据相关 tag 来处理 IndexDB 中的数据请求。
下面是一个聊天应用的场景
index.html:
btnSend.addEventListener('click', async () => {await db.add('chatList', { msg, time, useId});reg = await navigator.serviceWorker.ready;reg.sync.register('send_chat');
})
sw.js
self.addEventListener('sync', e => {e.tag == 'send_chat' && e.waitUntil(new Promise.then(async (res, rej) => {var allData = await db.getAll('chatList');return Promise.all(allData.map(data => fetch(data)));}))
})
2. 失败请求的 Sync 化
这个场景可以针对于某些特定请求,先让它正常发送网络请求,如果失败则将失败的请求放到相关的 IndexDB 中,并设定这条网络请求可尝试的有效期,有效期内均会重拾。
关于 sync 的周期上面也说过,在 Chrome 下最多尝试三次,本场景下的这种需求,需要相关的 sync tag 一直处理可用状态,所以需要对这一层进行修改满足需求。
例如点赞场景
index.html
btnLike.addEventListener('click', () => {reg = await navigator.serviceWorker.ready;reg.sync.register('like’);fetch(data).catch(e => {db.add('likeList', {data, lastTime: 12938749138}); // 有效期时间戳})
})
sw.js
self.addEventListener("sync", e => {if (e.tag == "send_chat") {e.waitUntil(new Promise.then(async (res, rej) => {while (db.get("likeList")[0]) {var data = db.get("likeList")[0];try {if(data.lastTime > Date.now()) {db.remove('likeList', data)} else {await fetch(data);db.remove('likeList', data)} } catch (err) {if(e.lastChance == true) { // 如果最后一次尝试机会,则重新注册,保证一直有效self.registration.sync.register('like')}}}}));}
});
注意:上面代码中的 db 为模拟的伪代码。
通过 Sync API 的支持,网站应用可以较大的离线化,提升用户的体验度和应用的可靠性。这样用户在网络连接失败的情况下也能上传表单、点赞评论文章、发送消息等等,为用户带来积极的影响。也意味着 Web 应用更加接近原生应用的体验效果。
博客名称:王乐平博客
CSDN博客地址:http://blog.csdn.net/lecepin