渐进式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);
})
})
)
});
};
渐进式web应用开发-- 使用后台同步保证离线功能(六)的更多相关文章
- 渐进式web应用开发---Service Worker 与页面通信(七)
_ 阅读目录 一:页面窗口向 service worker 通信 二:service worker 向所有打开的窗口页面通信 三:service worker 向特定的窗口通信 四:学习 Messag ...
- Asp.Net Web API开发微信后台
如果说用Asp.Net开发微信后台是非主流,那么Asp.Net Web API的微信后台绝对是不走寻常路. 需要说明的是,本人认为Asp.Net Web API在开发很多不同的请求方法的Restful ...
- 渐进式web应用开发---service worker 原理及介绍(一)
渐进式web应用(progressive Web app) 是现代web应用的一种新形式.它利用了最新的web功能,结合了原生移动应用的独特特性与web的优点,为用户带来了新的体验. 一:传统web端 ...
- 渐进式web应用开发--拥抱离线优先(三)
_ 阅读目录 一:什么是离线优先? 二:常用的缓存模式 三:混合与匹配,创造新模式 四:规划缓存策略 五:实现缓存策略 回到顶部 一:什么是离线优先? 传统的web应用完全依赖于服务器端,比如像很早以 ...
- 渐进式web应用开发---service worker (二)
阅读目录 1. 创建第一个service worker 及环境搭建 2. 使用service worker 对请求拦截 3. 从web获取内容 4. 捕获离线请求 5. 创建html响应 6. 理解 ...
- 渐进式web应用开发---ajax本地数据存储(四)
在前几篇文章中,我们使用service worker一步步优化了我们的页面,现在我们学习使用我们之前的indexedDB, 来缓存我们的ajax请求,第一次访问页面的时候,我们请求ajax,当我们继续 ...
- 渐进式web应用开发---promise式数据库(五)
在前面的一篇文章中,我们已经实现了使用indexedDB实现ajax本地数据存储的功能,详情,请看这篇文章.现在我们需要把上面的一篇文章中的代码使用promise结构来重构下.我们为什么需要使用pro ...
- 渐进式web应用开发---使用indexedDB实现ajax本地数据存储(四)
在前几篇文章中,我们使用service worker一步步优化了我们的页面,现在我们学习使用我们之前的indexedDB, 来缓存我们的ajax请求,第一次访问页面的时候,我们请求ajax,当我们继续 ...
- Django web框架开发基础-django实现留言板功能
1.创建项目 cmd django-admin startpoject cloudms 2.创建APP cmd django-admin startapp msgapp 3.修改settings,T ...
随机推荐
- 小程序请求接口统一封装到一个js文件中
在我们做小程序时,数据请求数据请求是避免不了的,然而我们用官方自带的请求方式,会给我们带来很多重复的工作,所以我就借鉴大神们的博客,写了一个简单的请求方式. 1.首先我们在utils中新建一个api. ...
- springboot 2.X 在访问静态资源的的时候出现404的问题
通过idea快速搭建一个springboot项目: springboot版本2.1.6 在网上看的资料,springboot静态资源访问如下: "classpath:/META‐INF/re ...
- Spark学习之路(十一)—— Spark SQL 聚合函数 Aggregations
一.简单聚合 1.1 数据准备 // 需要导入spark sql内置的函数包 import org.apache.spark.sql.functions._ val spark = SparkSess ...
- 【数据结构】红黑树-Java实现
WIKI:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree 转:红黑树(五)之 Java的实现 总结的比较精炼的: http://www.cnb ...
- Neo4j 爬坑笔记for3.2.6
官网语法,非常详尽:http://neo4j.com/docs/developer-manual/current/cypher/clauses/match/ A:请对应版本号,不同大版本可能会有很大区 ...
- 为什么string是引用类型 值还不可以修改
C#把数据类型分为值类型和引用类型.值类型操作简单,引用类型更省空间. C#一共有15个预定义类型,其中13个值类型(8个整型.2个浮点类型.decimal.bool.char),2个引用类型(str ...
- MyBatis从入门到精通(四):MyBatis XML方式的基本用法之增删改
最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 1. insert用法 1.1 简单的 ...
- What?一个 Dubbo 服务启动要两个小时!
前言 前几天在测试环境碰到一个非常奇怪的与 dubbo 相关的问题,事后我在网上搜索了一圈并没有发现类似的帖子或文章,于是便有了这篇. 希望对还未碰到或正在碰到的朋友有所帮助. 现象 现象是这样的,有 ...
- CQRS之旅——旅程8(后记:经验教训)
旅程8:后记:经验教训 我们的地图有多好?我们走了多远?我们学到了什么?我们迷路了吗? "这片土地可能对那些愿意冒险的人有益."亨利.哈德逊 这一章总结了我们旅程中的发现.它强调了 ...
- KVM虚拟机迁移至VMware
1.将kvm下虚拟机关机: [root@localhost ~]# virsh list --all Id Name State ----------------------------------- ...