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


    阅读目录

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

    在我们做移动端开发也好,还是做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源码查看

  • 相关阅读:
    MyEclipse 自带的TomCat 新增部署的时候不显示 Deploy Location
    No prohects are avaliable for deployment to this server
    Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
    Dom4j 对XMl解析 新手学习,欢迎高手指正
    电脑的技巧
    Browserify的基本使用
    bower的基本使用
    前端工程化--前端工程化技术栈
    前端工程化--架构说明
    前端工程化-前端工程化说明
  • 原文地址:https://www.cnblogs.com/tugenhua0707/p/11266082.html
Copyright © 2020-2023  润新知