閱讀目錄
一:什麼是後臺同步保證離線功能呢?
在我們做移動端開發也好,還是做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); }) }) ) }); };