PWA(Progressive Web App)入门系列:Sync 后台同步

1,523 阅读4分钟

前言

当我们在一些地下停车场,或者在火车上、电梯等无法避免的信号不稳定的场所,使用网站应用处理一些表单操作或者上传数据的操作时,面临的将是网络连接错误的响应,使用户的操作白费。

而此刻 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博客地址:blog.csdn.net/lecepin

知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。