漸進式web應用開發-- 使用後臺同步保證離線功能(六)

閱讀目錄html

一:什麼是後臺同步保證離線功能呢?前端

在咱們作移動端開發也好,仍是作PC端應用也好,咱們常常會碰到填寫表單這樣的功能,若是咱們的表單填寫完成之後,咱們點擊提交,可是這個時候我忽然進入了電梯,或者咱們在高鐵上作這麼一個操做,忽然斷網了,或者說咱們的網絡很差的狀況下,那麼通常的狀況下會一直請求,當咱們的請求超時的時候就會請求失敗,或者說請求異常,最後就會提示咱們網絡異常這些信息,那麼這樣對於用戶體驗來講並非很好,那麼如今咱們來理解下什麼是後臺同步保證離線功能呢?後臺同步離線功能就是說當咱們的網絡很差的時候,咱們點擊提交按鈕時,咱們會保證該應用必定是成功的,不會提示網絡異常這些信息,當網絡鏈接失敗的時候,咱們會在前端頁面顯示一個提示,好比說,正在請求中,請稍微.... 這樣的一個提示,當咱們的網絡恢復正常了,咱們會從新去請求下該接口,那麼咱們這個應用就提示操做成功狀態了。node

後臺同步:它使得咱們可以確保用戶採起的任何操做都能完成,無論用戶的連接狀態如何,甚至當用戶點擊提交後,直接關閉咱們這個應用,再也不回來,而且關閉瀏覽器,後臺同步操做也可以完成。jquery

後臺同步的優勢:webpack

1. 對於用戶而言,可以信任咱們的漸進式web應用能一直工做,這也意味者咱們與傳統的web開發是有區別的,咱們能夠實現原生應用相似的效果。git

2. 對於企業來說,讓用戶在連接失敗的時候,也可以訂火車票,訂閱新聞或發送消息,對於這樣的用戶體驗也會更好。github

二:後臺同步是如何實現的呢?web

後臺同步原理的實質是:它是將操做從頁面上下文中剝離開來,而且在後臺運行。ajax

經過將這些操做放到後臺,它就不會受到單個網頁的影響,即便網頁被關閉,用戶鏈接會斷開,甚至服務器有時候會出現故障,可是隻要咱們電腦上安裝了瀏覽器,後臺同步的操做就不會消失,直到它成功完成爲止。數據庫

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源碼查看

相關文章
相關標籤/搜索