代码改变世界

渐进式web应用开发-- 使用后台同步保证离线功能(六)

2019-07-29 19:52  龙恩0707  阅读(1927)  评论(1编辑  收藏  举报

阅读目录

一:什么是后台同步保证离线功能呢?

在我们做移动端开发也好,还是做PC端应用也好,我们经常会碰到填写表单这样的功能,如果我们的表单填写完成以后,我们点击提交,但是这个时候我突然进入了电梯,或者我们在高铁上做这么一个操作,突然断网了,或者说我们的网络不好的情况下,那么一般的情况下会一直请求,当我们的请求超时的时候就会请求失败,或者说请求异常,最后就会提示我们网络异常这些信息,那么这样对于用户体验来说并不是很好,那么现在我们来理解下什么是后台同步保证离线功能呢?后台同步离线功能就是说当我们的网络不好的时候,我们点击提交按钮时,我们会保证该应用一定是成功的,不会提示网络异常这些信息,当网络连接失败的时候,我们会在前端页面显示一个提示,比如说,正在请求中,请稍微.... 这样的一个提示,当我们的网络恢复正常了,我们会重新去请求下该接口,那么我们这个应用就提示操作成功状态了。

后台同步:它使得我们能够确保用户采取的任何操作都能完成,不管用户的链接状态如何,甚至当用户点击提交后,直接关闭我们这个应用,不再回来,并且关闭浏览器,后台同步操作也能够完成。

后台同步的优点:

1. 对于用户而言,能够信任我们的渐进式web应用能一直工作,这也意味者我们与传统的web开发是有区别的,我们可以实现原生应用类似的效果。

2. 对于企业来讲,让用户在链接失败的时候,也能够订火车票,订阅新闻或发送消息,对于这样的用户体验也会更好。

二:后台同步是如何实现的呢?

后台同步原理的实质是:它是将操作从页面上下文中剥离开来,并且在后台运行。

通过将这些操作放到后台,它就不会受到单个网页的影响,即使网页被关闭,用户连接会断开,甚至服务器有时候会出现故障,但是只要我们电脑上安装了浏览器,后台同步的操作就不会消失,直到它成功完成为止。

1. 注册一个同步事件

使用后台同步很简单,我们首先要注册一个同步事件,如下代码:

navigator.serviceWorker.ready.then(function(registration) {
  registration.sync.register('send-messages');
});

如上代码可以在页面上运行,它获取了当前激活的service Worker 的 registration 对象,并注册了一个叫 send-messages 的sync事件。

现在,我们就可以将一个监听该同步事件的事件监听器添加到 service worker 中,该事件包含的逻辑将会在service worker 中执行,而不是在页面上执行的。

如下代码:

self.addEventListener("sync", function(event) {
  if (event.tag === "send-messages") {
    event.waitUntil(function(){
      var sent = sendMessages();
      if (sent) {
        return Promise.resolve();
      } else {
        return Promise reject();
      }
    })
  }
});

2. 理解 SyncManager

我们上面已经注册了一个sync事件,并且在service worker中监听了该sync事件。
那么所有与sync事件的交互都是通过 SyncManager 来完成的。SyncManager是 service worker 的一个接口。它可以让我们注册sync事件,并且我们可以获取已经注册的sync事件列表。

访问 SyncManager

我们可以通过已经激活的service worker的registration对象来访问 SyncManager, 在service worker里面,我们可以通过调用navigator.serviceWorker.ready 来访问当前激活的 service worker 的 registration对象,该方法会返回一个Promise对象,当成功时候我们可以拿到service worker的registration对象。

如下代码:

navigator.serviceWorker.ready.then(function(registration){});

如上代码,我们已经获得到了 registration 对象后,不管我们是在service worker上还是在页面上,和SyncManager交互现在都是一样的。

3. 注册事件

想要注册 sync事件,我们可以在 SyncManager中调用 register, 传入一个我们想要注册的 sync事件名称。

比如我们想注册一个在service worker中叫 send-message的事件,我们可以使用如下代码:

self.registration.sync.register("send-message");

如果我们想在service-worker中想做一样的事情的话,我们可以使用如下代码:

navigator.serviceWorker.ready.then(function(registration) {
  registration.sync.register("send-message");
});

4. 理解Sync事件原理

SyncManager 维护了一个Sync事件标签列表,SyncManager只知道哪些事件被注册了,何时被调用,以及如何发送sync事件。

当我们下面任何一个事件发生的时候,SyncManager会给列表中的每一个注册事件名发送一个sync事件。

1) sync事件注册后会立即发送。
2)当用户离线变成在线的时候也会发送。
3)如果还有未完成的事件时,每隔几分钟会发送。

在service worker中,我们发送sync事件,那么该事件就可以被监听到,并且我们可以使用promise进行响应,如果我们的这个promise完成了,那么对应的sync注册会从 SyncManager中删除,如果promise拒绝了,那么我们的sync注册的事件就会保留在SyncManager中,并且每隔几分钟会在下一个同步机会进行重试。

5. 理解 sync事件中的事件名称

sync事件中的事件名称是唯一的。如果在SyncManager中使用一个已经被注册的事件名称继续来注册的话,那么SyncManager 会忽略它,比如说:我们正在构建一个邮件服务,每当用户发送消息的时候,我们可以把消息保存到 indexedDB的发件箱中,并且注册一个send-email-message这样的后台同步事件,那么我们的service worker可以包含一个事件监听器进行监听,它会遍历indexedDB发件箱中的每一条消息,尝试发送他们,并且当发送成功后,将会从 indexedDB队列中删除它,如果我们当中有某条消息并没有发送成功的话,那么该sync事件就会被拒绝,SyncManager将会在稍后再次发送该事件,但是该事件是我们上次事件中发送失败的那个事件。使用这种设置,我们永远不需要检查发件箱中是否存在消息,只要有未发送的电子邮件,sync事件就会保持注册,并且尝试清空我们发的发件箱。

5. 理解获取已经注册的sync事件列表

我们使用SyncManager的getTags()方法,就可以得到完整的已注册同步事件列表。该getTags()方法也会返回一个Promise对象,该promise对象完成后,会获得一个sync注册事件名称的数组。

在service-worker中,我们可以注册一个叫 hello-world的sync事件,然后将当前注册的完整事件列表打印在控制台中中;如下代码:

self.registration.sync.register("hello-world").then(function() {
  return self.registration.sync.getTags();
}).then(function(tags) {
  console.log(tags);
});

在我们的service worker中,我们首先通过使用 ready 获取 registration对象,也可以获取一样的结果,如下代码所示:

navigator.serviceWorker.ready.then(function(registration){
  registration.sync.register('hello-world').then(function() {
    return registration.sync.getTags();
  }).then(function(tags) {
    console.log(tags);
  });
});

6. 最后一次发生sync事件

在有些情况下,SyncManager可以会判断出尝试发送的sync事件已经多次失败,当发生这种情况的时候,SyncManager将会发送最后一次事件,给我们最后一次响应的机会,我们可以通过sync事件的lastChance属性来判断什么时候会发生这种情况,如下代码:

self.addEventListener("sync", event => {
  if (event.tag === "hello-world") {
    event.waitUntil(
      // 调用 addReservation方法
      addReservation().then(function(){
        return Promise.resolve();
      }).catch(function(error) {
        if (event.lastChance) {
          return removeReservation();
        } else {
          return Promise.reject();
        }
      })
    )
  }
})

三:如何给sync事件传递数据?

在页面接口交互中,我们可能需要传递一些参数进去,比如说,发生一个消息的接口中,可能我们需要把消息文本发送过去,一个为帖子点赞的接口,我们需要把帖子id的参数传递过去。但是当我们注册sync事件时,我们目前来看,我们之前只能传递事件名称,但是我们如何把一些对应的参数也传递给sync中的事件当中呢?

1. 在indexedDB中维护操作队列

要想把一些参数传递过去,我们可以把这些参数先保存到我们的indexedDB中,然后,我们在service worker中的sync事件代码我们可以迭代该对象存储,并且在每个条目上执行所需的操作,一旦操作成功了,我们就可以把该实体从对象存储中删除掉。

现在我们来做个demo,我们现在需要把每一条消息可以添加到 message-queue对象存储中,然后我们注册一个 send-message 后台同步事件来处理,该事件会遍历 message-queue对象中的所有消息,依次将他们发送到网络中,如果当所有消息都发送成功的话,我们会依次将消息队列中的数据删除,因此对象存储就为空了。但是如果有任何一条消息没有发送成功的话,就会向sync事件返回一个拒绝的promsie,SyncManager在稍后一段时间内会再次运行该sync事件。

如果我们之前使用如下代码,来请求一个接口,如下代码所示:

var sendMessage = function(subject, message) {
  fetch('/new-message', {
    method: 'post',
    body: JSON.stringify({
      subject: subject,
      msg: message
    })
  })
};

现在我们使用service worker,需要把代码改成如下所示:

var triggerMessageQueueUpdate = function() {
  navigator.serviceWorker.ready.then(function(registration) {
    registration.sync.register("message-queue-sync");
  });
};

var sendMessage = function(subject, message) {
  addToObjectStore("message-queue", {
    subject: subject,
    msg: message
  });
  triggerMessageQueueUpdate();
};

然后我们需要在service worker中监听sync事件代码如下:

self.addEventListener("sync", function(event) {
  if (event.target === 'message-queue-sync') {
    event.waitUntil(function() {
      return getAllMessages().then(function(messages) {
        return Promise.all(
          messages.map(function(message) {
            return fetch('/new-message', {
              method: 'post',
              body: JSON.stringify({
                subject: subject,
                msg: message
              })
            }).then(function(){
              return deleteMessageFromQueue(message); 
            })
          })
        )
      })
    })
  }
});

如上改写后的代码,首先我们会调用 addToObjectStore 这个方法来把消息保存到我们的key为 'message-queue' 当中,然后调用 triggerMessageQueueUpdate 这个方法,使用sync注册message-queue-sync这个事件,并且我们使用sync监听了该事件名称,然后我们使用了 getAllMessages 方法获取indexedDB的消息队列中的所有消息,并且最终返回了一个promise给sync事件,在该代码中,我们使用了Promise.all方法,在该方法内部,只有我们的消息发送成功后,我们才会使用 deleteMessageFromQueue方法来删除该消息,在我们的消息数组中,我们使用了map()方法遍历为每条消息发送一个promise对象.

2. 在indexedDB中维护请求队列

有时候在我们的项目中,我们需要实现本地存储架构来对对象状态进行跟踪,如果页面上有多个ajax请求的话,我们可以使用service worker 在indexedDB中来维护请求队列,我们可以将网络上的每个请求存储到indexedDB中,然后该方法会注册一个sync事件,该事件会遍历对象存储中所有请求,并依次执行。

比如项目中有如下代码:

var sendMessage = function(subject, message) {
  fetch('/new-message', {
    method: 'post',
    body: JSON.stringify({
      subject: subject,
      msg: message
    })
  })
};

var getRequest = function(id) {
  fetch('/like-post?id='+id);
};

如上两个请求,我们使用service worker 换成如下代码:

var triggerRequestQueueSync = function() {
  navigator.serviceWorker.ready.then(function(registration){
    registration.sync.register("request-queue");
  });
};

var sendMessage = function(subject, message) {
  addToObjectStore("request-queue", {
    url: '/new-message',
    method: 'post',
    body: JSON.stringify({
      subject: subject,
      msg: message
    })
  });
  triggerRequestQueueSync();
};

var getRequest = function(id) {
  addToObjectStore('request-queue', {
    url: '/like-post?id=' + id,
    method: 'get'
  });
  triggerRequestQueueSync();
};

如上代码,我们将所有的网络请求替换成如上的代码,将代表请求对象存储到 request-queue的对象存储中,这个存储中每个对象代表一个网络请求,接下来我们需要添加一个sync事件监听器到service worker中,它负责遍历 request-queue的所有请求,依次会发起一个网络请求,发送成功后,依次从对象存储中删除。如下代码所示:

self.addEventListener("sync", function(event) {
  if (event.tag === "request-queue") {
    event.waitUntil(function(){
      return getAllObjectsFrom("request-queue").then(function(requests) {
        return Promise.all(
          requests.map(function(req) {
            return fetch(req.url, {
              method: req.method,
              body: req.body
            }).then(function() {
              return deleteRequestFromQueue(req); // 返回一个promise
            })
          })
        )
      });
    })
  }
});

如上代码,如果一个请求发送成功了,就会从indexedDB中队列中删除掉,失败的请求会保留到队列中,并且返回被拒绝的promise,那么失败的promise会在请求队列的下一次sync事件中再次迭代。

3. 使用一种更简单的方式传递数据给事件名称

当我们需要传递一个简单的数据给sync函数时候,我们就不需要使用上面的indexedDB来存储数据,然后再service worker中依次遍历拿到该对象了,我们可以使用一种更简单的方式来解决如上的问题。我们之前的代码是这样的:

var likePost = function(postId) {
  fetch("/like-post?id="+postId);
};

我们可以在service worker中使用如下代码来进行改造,如下代码所示:

var likePost = function(postId) {
  navigator.serviceWorker.ready.then(function(registration){
    registration.sync.register("like-post-"+postId);
  });
};

我们使用sync事件来监听上面的函数,代码如下:

self.addEventListener("sync", function(event) {
  if (event.tag.startsWith("like-post-")) {  
    event.waitUntil(function(){
      var postId = event.tag.slice(10);
      return fetch('/like-post?id='+postId);
    })
  }
});

四:在我们的项目中添加后台同步功能

在我们项目中添加后台同步功能之前,我们还是来看下我们项目中的整个目录架构如下所示:

|----- service-worker-demo6
|  |--- node_modules        # 项目依赖的包
|  |--- public              # 存放静态资源文件
|  | |--- js
|  | | |--- main.js         # js 的入口文件
|  | | |--- store.js        # indexedDB存储
|  | | |--- myAccount.js    
|  | |--- styles
|  | |--- images
|  | |--- index.html        # html 文件
|  |--- package.json
|  |--- webpack.config.js
|  |--- sw.js

该篇文章是在上篇文章基础之上继续扩展的,如果想要看上篇文章,请点击这里

我们首先来看下我们 public/index.html 代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>service worker 实列</title>
</head>
<body>
  <div id="app">222226666</div>
  <img src="/public/images/xxx.jpg" />
  <div style="cursor: pointer;color:red;font-size:18px;padding:5px;border:1px solid #333;" id="submit">点击我新增</div>

  <div style="cursor: pointer;color:red;font-size:18px;padding:5px;border:1px solid #333;" id="update">点击我修改</div>

</body>
</html>

public/js/myAccout.js 代码如下(该代码的作用最主要是做页面业务逻辑代码)

import $ from 'jquery';

$(function() {
  function renderHTMLFunc(obj) {
    console.log(obj);
  }
  function updateDisplay(d) {
    console.log(d);
  };
  var addStore = function(id, name, age) {
    var obj = {
      id: id,
      name: name,
      age: age
    };
    addToObjectStore("store", obj);
    renderHTMLFunc(obj);
    $.getJSON("http://localhost:8081/public/json/index.json", obj, function(data) {
      updateDisplay(data);
    });
  };
  $("#submit").click(function(e) {
    addStore(3, 'longen1', '111');
  });
  $("#update").click(function(e) {
    $.getJSON("http://localhost:8081/public/json/index.json", {id: 1}, function(data) {
      updateInObjectStore("store", 1, data);
      updateDisplay(data);
    });
  });
});

如上myAccout.js代码,当我点击 id 为 "submit" 的div元素的时候(我们可以假设这是一个form表单提交,这边为了演示这个作用懒得使用form表单来演示),当我点击该div元素的时候,我们的addStore函数会被调用,这个函数内部会调用 addToObjectStore()这个方法,这个函数会添加一个store对象的存储,它会把该对象添加到IndexedDB的store对象中,添加完成以后,我们会调用renderHTMLFunc() 这个方法来渲染我们的html页面,并且之后我们会发起一个ajax请求。如果我们的网络一直是可以用的话,那么我们就不需要做任何处理操作,但是如果我们的网络连接失败的情况下,我们调用了 addStore 方法,那么我们新的数据会被添加到indexedDB中,并且会调用renderHTMLFunc方法来渲染我们的页面,但是后面的ajax请求就会调用失败。页面虽然更新了,indexedDB数据也保存到本地了,但是我们的服务器完全不知情,因此在这种情况下,我们需要使用service worker中的sync事件来解决这个问题。

我们要完成如下步骤:

1)在addStore函数添加代码,检查浏览器是否支持后台同步。如果支持,则注册一个 sync-store 同步事件,否则的话,便使用长规的ajax调用。

2)在store.js 中,添加到indexedDB代码,需要把状态改为 sending(发送中),在发送请求到服务器之前,这就是用户看到的状态,操作成功后,服务器会返回新的状态。

3)我们会向service worker添加一个事件监听器,用来监听sync事件,如果我们检测到sync的事件名称是 sync-store ,事件监听器就会遍历每一个处于 sending 状态的预订,并且尝试发送给服务器。成功添加到服务器之后,indexedDB中的状态就会被修改成为新的状态,如果任何服务器请求失败的话,那么整个sync事件就会被拒绝,浏览器就会尝试在随后再运行这个事件了。

因此我们现在的第一步是在 addStore函数中,添加浏览器是否支持同步功能,如果支持的话,就会注册一个sync事件。如下代码(在myAccount.js 代码修改):

var addStore = function(id, name, age) {
  var obj = {
    id: id,
    name: name,
    age: age
  };
  addToObjectStore("store", obj);
  renderHTMLFunc(obj);
  // 先判断浏览器支付支持sync事件
  if ("serviceWorker" in navigator && "SyncManager" in window) {
    navigator.serviceWorker.ready.then(function(registration) {
      registration.sync.register("sync-store")
    });
  } else {
    $.getJSON("http://localhost:8081/public/json/index.json", obj, function(data) {
      updateDisplay(data);
    });
  }
};

因此我们的 public/js/myAccount.js 所有的代码如下:

import $ from 'jquery';

$(function() {
  function renderHTMLFunc(obj) {
    console.log(obj);
  }
  function updateDisplay(d) {
    console.log(d);
  };
  var addStore = function(id, name, age) {
    var obj = {
      id: id,
      name: name,
      age: age
    };
    addToObjectStore("store", obj);
    renderHTMLFunc(obj);
    // 先判断浏览器支付支持sync事件
    if ("serviceWorker" in navigator && "SyncManager" in window) {
      navigator.serviceWorker.ready.then(function(registration) {
        registration.sync.register("sync-store").then(function() {
          console.log("后台同步已触发");
        }).catch(function(err){
          console.log('后台同步触发失败', err);
        })
      });
    } else {
      $.getJSON("http://localhost:8081/public/json/index.json", obj, function(data) {
        updateDisplay(data);
      });
    }
  };
  $("#submit").click(function(e) {
    addStore(3, 'longen1', '111');
  });
  $("#update").click(function(e) {
    $.getJSON("http://localhost:8081/public/json/index.json", {id: 1}, function(data) {
      updateInObjectStore("store", 1, data);
      updateDisplay(data);
    });
  });
});

2)其次我们需要在 public/js/store.js 中openDataBase方法中的 result.onupgradeneeded 函数代码改成如下(当然要触发该函数,我们需要升级我们的版本号,即把 var DB_VERSION = 2; 把之前的 DB_VERSION 值为1 改成2):

var DB_VERSION = 2;
var DB_NAME = 'store-data2';

// 监听当前版本号被升级的时候触发该函数
result.onupgradeneeded = function(event) {
  var db = event.target.result;
  var upgradeTransaction = event.target.transaction;
  var reservationsStore;
  /*
   是否包含该对象仓库名(或叫表名)。如果不包含就创建一个。
   该对象中的 keyPath属性id为主键
  */
  if (!db.objectStoreNames.contains('store')) {
    reservationsStore = db.createObjectStore("store", { keyPath: "id", autoIncrement: true });
  } else {
    reservationsStore = upgradeTransaction.objectStore("store");
  }
  if (!reservationsStore.indexNames.contains("idx_status")) {
    reservationsStore.createIndex("idx_status", "status", {unique: false});
  }
}

如上代码,我们在创建 store对象之前,我们会先判断该对象是否存在,如果不存在的话,我们会创建该对象,否则的话,我们就通过调用 event.target.transaction.objectStore("store")将获得更新事件中的事务,并且从事务中获取store对象存储的引用。

最后我们确认我们的store对象存储是否已经有 idx_status 这个,如果不存在的话,如果不存在的话,我们就创建该索引。

3)现在我们需要修改我们的 public/js/store.js 中的getStore函数了,我们在该函数内部使用这个新的索引,就可以获取到该某个请求中的某个状态了,因此我们要对 我们的 getStore函数进行修改,让其支持接收两个可选的参数,索引名称,以及传递给该索引的值。如下代码的修改:

var getStore = function (indexName, indexValue) {

  return new Promise(function(resolve, reject) {
    openDataBase().then(function(db) {
      var objectStore = openObjectStore(db, 'store');
      var datas = [];
      var cursor;
      if (indexName && indexValue) {
        cursor = objectStore.index(indexName).openCursor(indexValue);
      } else {
        cursor = objectStore.openCursor();
      }

      cursor.onsuccess = function(event) {
        var cursor = event.target.result;
        if (cursor) {
          datas.push(cursor.value);
          cursor.continue();
        } else {
          if (datas.length > 0) {
            resolve(datas);
          } else {
            getDataFromServer().then(function(d) {
              openDataBase().then(function(db) {
                var objectStore = openObjectStore(db, "store", "readwrite");
                for (let i = 0; i < datas.length; i++) {
                  objectStore.add(datas[i]);
                }
                resolve(datas);
              });
            });
          }
        }
      }
    }).catch(function() {
      getDataFromServer().then(function(datas) {
        resolve(datas);
      });
    });
  });
};

如上代码,我们对getStore函数接受了两个可选的新参数(indexName和indexValue)。其次,如果我们的函数接收了这些参数的话,就使用参数在特定的索引(indexName)上打开流标,然后打开特定值(indexValue)的流标,会把结果限定在指定的范围内。如果没有传递这些参数的话,它会向以前那样运行。

做出这两个地方的修改,我们的函数可以返回所有的结果,也可以返回结果中的一个子集,如下代码所示:

getStore().then(function(reservations){
  // reservations 包含了所有的数据
});

getStore("idx_status", "Sending").then(function() {
  // reservations 变量仅仅包含了状态为 "Sending" 的数据
});

4)现在我们需要在我们的 sw.js 中添加后台同步的事件监听器到 service worker中了。

首先我们在我们的sw.js中引入 store.js ,代码如下所示:

importScripts("/public/js/store.js");

然后在我们sw.js的底部,添加如下代码:

var createStoreUrl = function(storeDetails) {
  var storeUrl = new URL("http://localhost:8081/public/json/index.json");
  Object.keys(storeDetails).forEach(function(key) {
    storeUrl.searchParams.append(key, storeDetails[key]);
  });
  return storeUrl;
};

var syncStores = function() {
  return getStore("idx_status", "Sending").then(function(reservations) {
    return Promise.all(
      reservations.map(function(reservation){
        var reservationUrl = createStoreUrl(reservation);
        return fetch(reservationUrl);
      })
    )
  });
};

self.addEventListener("sync", function(event) {
  if (event.tag === "sync-store") {
    event.waitUntil(syncStores());
  }
});

如上代码,我们使用 self.addEventListener 为sync事件添加一个新的事件监听器,这个事件监听器会响应事件名称为 "sync-store" 的事件,然后使用 waitUntil方法等待 syncStores()函数返回的promise,会根据该promise的完成或拒绝来判断sync事件是完成还是拒绝,如果是完成的话,那么 sync-store的sync事件就会从SyncManager中删除,如果promise是拒绝的话,那么我们的SyncManager会保持 sync事件的注册,并且在随后会再次触发该事件。

syncStores() 会遍历IndexedDB中每一个标记为"Sending" 状态的数据,尝试会再次发送到服务器,并且返回一个promise,只有当promise发送成功了的话,那么就会完成状态。

然后在我们的getStore函数中,可以获取所有处于 Sending 状态的数据,该函数也返回了一个promise对象,该promise会决定整个syncStores函数的结果。要实现这点,我们使用了Promise.all()传入了一个promise数组,我们拿到该数据对象数组后,会通过Array.map()方法将数组的元素转化为 promise,我们使用 map对每个元素进行迭代,创建一个fetch请求发送到服务器来创建这个请求,fetch也会返回一个promise。

createStoreUrl 函数使用URL接口创建了一个新的URL对象,这个对象表示的是fetch请求接口,使用这种方式更优雅的创建带有查询字符串的URL,比如如下代码会打印带参数的url。

console.log(createStoreUrl({'name': 'kongzhi', 'age': 30}));

那么打印的 结构就是:http://localhost:8081/public/json/index.json?name=kongzhi&age=30; 这样的了。

完成上面的代码后,我们来打开我们的应用 http://localhost:8081/ 刷新下,然后我们把我们的网络断开,然后再点击 "点击我新增"这个文字,就会在控制台上打印如下信息了;如下图所示:

然后我们打开我们的网络,没过一会儿,就可以看到我们的请求会自动请求一次,如下图所示:

请求成功后,我们就可以把页面的消息 "请求加载中, 请稍后..." 这几个字 可以改成 "请求成功了..." 这样的提示了。

如上代码后,当我们的网络恢复完成后,我们会重新发ajax请求,请求完成后,可能会有新的请求状态数据,因此我们现在最后一步是需要更新我们的indexedDB数据库了,以便显示最新的消息给我们的用户,并且我们要更新我们的数据状态,等我们下一次 sync-store 事件注册的时候,不会重新发送。因此我们需要改变syncStores函数代码:

在更新之前,我们之前的代码是如下这样的:

var syncStores = function() {
  return getStore("idx_status", "Sending").then(function(reservations) {
    console.log(reservations);
    return Promise.all(
      reservations.map(function(reservation){
        var reservationUrl = createStoreUrl(reservation);
        return fetch(reservationUrl);
      })
    )
  });
};

更新之后的代码如下所示:

var syncStores = function() {
  return getStore("idx_status", "Sending").then(function(reservations) {
    console.log(reservations);
    return Promise.all(
      reservations.map(function(reservation){
        var reservationUrl = createStoreUrl(reservation);
        return fetch(reservationUrl).then(function(response) {
          return response.json();
        }).then(function(newResponse) {
          return updateInObjectStore("store", 1, newResponse);
        })
      })
    )
  });
};

github源码查看