Service Worker:如何同步queue?
Service Worker: how to do a synchronous queue?
我有一个 Service Worker 从 Firebase FCM 接收推送消息。它们会导致通知显示或取消。用户可以有多个设备(这就是取消的目的:当用户已经对通知 A 采取行动时,我会尝试在所有设备上关闭它)。
我遇到的问题是用户的其中一台设备离线或完全关闭。设备上线后,firebase 会传送它之前无法传送的所有消息。因此,例如,您会得到:
- 显示内容为 X 的通知 A
- 显示内容为 Y 的通知 A(替换通知 A)
- 显示内容为 Z 的通知 B
- 取消通知 A
SW 快速连续接收这些消息。问题是取消通知比显示通知快得多(~2 毫秒对 16 毫秒)。所以第四条消息在第一条(或第二条)消息实际创建通知之前被处理,结果是通知没有被取消。
// 编辑: 下面是经过大量编辑的问题。添加了示例代码并分解了我的问题。还编辑了标题以更好地反映我实际的基本问题。
我尝试将消息推送到 queue 中并一条一条地处理它们。事实证明这可能会变得有点复杂,因为 SW 中的所有内容都是异步的,更糟糕的是,当浏览器认为 SW 完成其工作时,它可以随时被终止。我试图以持久的方式存储 queue,但由于 LocalStorage 在 SW 中不可用,我需要使用异步 IndexedDB API。更多可能导致问题的异步调用(如丢失物品)。
也有可能 event.waitUntil
认为我的工人在实际完成之前就已经完成了,因为我没有正确地 'passing the torch' 从承诺到承诺..
这是我尝试过的(很多)简化代码:
// Use localforage, simplified API for IndexedDB
importScripts("localforage.min.js");
// In memory..
var mQueue = []; // only accessed through get-/setQueue()
var mQueueBusy = false;
// Receive push messages..
self.addEventListener('push', function(event) {
var data = event.data.json().data;
event.waitUntil(addToQueue(data));
});
// Add to queue
function addToQueue(data) {
return new Promise(function(resolve, reject) {
// Get queue..
getQueue()
.then(function(queue) {
// Push + store..
queue.push(data);
setQueue(queue)
.then(function(queue){
handleQueue()
.then(function(){
resolve();
});
});
});
});
}
// Handle queue
function handleQueue(force) {
return new Promise(function(resolve, reject) {
// Check if busy
if (mQueueBusy && !force) {
resolve();
} else {
// Set busy..
mQueueBusy = true;
// Get queue..
getQueue()
.then(function(queue) {
// Check if we're done..
if (queue && queue.length<=0) {
resolve();
} else {
// Shift first item
var queuedData = queue.shift();
// Store before continuing..
setQueue(queue)
.then(function(queue){
// Now do work here..
doSomething(queuedData)
.then(function(){
// Call handleQueue with 'force=true' to go past (mQueueBusy)
resolve(handleQueue(true));
});
});
}
});
}
});
}
// Get queue
function getQueue() {
return new Promise(function(resolve, reject) {
// Get from memory if it's there..
if (mQueue && mQueue.length>0) {
resolve(mQueue);
}
// Read from indexed db..
else {
localforage.getItem("queue")
.then(function(val) {
var queue = (val) ? JSON.parse(val) : [];
mQueue = queue;
resolve(mQueue);
});
}
});
}
// Set queue
function setQueue(queue) {
return new Promise(function(resolve, reject) {
// Store queue to memory..
mQueue = queue;
// Write to indexed db..
localforage.setItem("queue", mQueue)
.then(function(){
resolve(mQueue);
});
});
}
// Do something..
function doSomething(queuedData) {
return new Promise(function(resolve, reject) {
// just print something and resolve
console.log(queuedData);
resolve();
});
}
我的问题的简短版本 - 考虑到我的特定 use-case - 是:我如何同步处理推送消息而不必使用更多异步 API?
如果我将这些问题分成多个:
- 我认为我需要 queue 那些消息是否正确?
- 如果是这样,如何处理 SW 中的 queues?
- 我不能(完全)依赖全局变量,因为 SW 可能会被杀死,我不能使用 LocalStorage 或类似的同步 API,所以我需要使用另一个异步 API 喜欢 IndexedDB 来做这个。这个假设是否正确?
- 我上面的代码是正确的方法吗?
- 有点相关:由于我需要将 event.waitUntil 从承诺传递到承诺,直到 queue 被处理,我在
handleQueue()
中调用 resolve(handleQueue())
是否正确保持下去?或者我应该做 return handleQueue()
?或者..?
只是为了理解 "why not use collapse_key":这是一个聊天应用程序,每个聊天室都有自己的标签。一个用户可以参与 4 个以上的聊天室,由于 firebase 将 collapse_keys 的数量限制为 4 个,我不能使用它。
所以我要大胆地说,将内容序列化到 IDB 可能有点矫枉过正。只要您等到所有待处理的工作都完成之后再解决传递给 event.waitUntil()
的承诺,服务工作者就应该保持活动状态。 (如果需要几分钟才能完成这项工作,那么 service worker 无论如何都有可能被杀死,但对于你所描述的情况,我认为这种风险很低。)
下面是我将如何构建您的代码的粗略草图,利用当前支持服务工作者的所有浏览器中的本机 async
/await
支持。
(我还没有实际测试过这些,但从概念上讲我认为它是合理的。)
// In your service-worker.js:
const isPushMessageHandlerRunning = false;
const queue = [];
self.addEventListener('push', event => {
var data = event.data.json().data;
event.waitUntil(queueData(data));
});
async function queueData(data) {
queue.push(data);
if (!isPushMessageHandlerRunning) {
await handlePushDataQueue();
}
}
async function handlePushDataQueue() {
isPushMessageHandlerRunning = true;
let data;
while(data = queue.shift()) {
// Await on something asynchronous, based on data.
// e.g. showNotification(), getNotifications() + notification.close(), etc.
await ...;
}
isPushMessageHandlerRunning = false;
}
我有一个 Service Worker 从 Firebase FCM 接收推送消息。它们会导致通知显示或取消。用户可以有多个设备(这就是取消的目的:当用户已经对通知 A 采取行动时,我会尝试在所有设备上关闭它)。
我遇到的问题是用户的其中一台设备离线或完全关闭。设备上线后,firebase 会传送它之前无法传送的所有消息。因此,例如,您会得到:
- 显示内容为 X 的通知 A
- 显示内容为 Y 的通知 A(替换通知 A)
- 显示内容为 Z 的通知 B
- 取消通知 A
SW 快速连续接收这些消息。问题是取消通知比显示通知快得多(~2 毫秒对 16 毫秒)。所以第四条消息在第一条(或第二条)消息实际创建通知之前被处理,结果是通知没有被取消。
// 编辑: 下面是经过大量编辑的问题。添加了示例代码并分解了我的问题。还编辑了标题以更好地反映我实际的基本问题。
我尝试将消息推送到 queue 中并一条一条地处理它们。事实证明这可能会变得有点复杂,因为 SW 中的所有内容都是异步的,更糟糕的是,当浏览器认为 SW 完成其工作时,它可以随时被终止。我试图以持久的方式存储 queue,但由于 LocalStorage 在 SW 中不可用,我需要使用异步 IndexedDB API。更多可能导致问题的异步调用(如丢失物品)。
也有可能 event.waitUntil
认为我的工人在实际完成之前就已经完成了,因为我没有正确地 'passing the torch' 从承诺到承诺..
这是我尝试过的(很多)简化代码:
// Use localforage, simplified API for IndexedDB
importScripts("localforage.min.js");
// In memory..
var mQueue = []; // only accessed through get-/setQueue()
var mQueueBusy = false;
// Receive push messages..
self.addEventListener('push', function(event) {
var data = event.data.json().data;
event.waitUntil(addToQueue(data));
});
// Add to queue
function addToQueue(data) {
return new Promise(function(resolve, reject) {
// Get queue..
getQueue()
.then(function(queue) {
// Push + store..
queue.push(data);
setQueue(queue)
.then(function(queue){
handleQueue()
.then(function(){
resolve();
});
});
});
});
}
// Handle queue
function handleQueue(force) {
return new Promise(function(resolve, reject) {
// Check if busy
if (mQueueBusy && !force) {
resolve();
} else {
// Set busy..
mQueueBusy = true;
// Get queue..
getQueue()
.then(function(queue) {
// Check if we're done..
if (queue && queue.length<=0) {
resolve();
} else {
// Shift first item
var queuedData = queue.shift();
// Store before continuing..
setQueue(queue)
.then(function(queue){
// Now do work here..
doSomething(queuedData)
.then(function(){
// Call handleQueue with 'force=true' to go past (mQueueBusy)
resolve(handleQueue(true));
});
});
}
});
}
});
}
// Get queue
function getQueue() {
return new Promise(function(resolve, reject) {
// Get from memory if it's there..
if (mQueue && mQueue.length>0) {
resolve(mQueue);
}
// Read from indexed db..
else {
localforage.getItem("queue")
.then(function(val) {
var queue = (val) ? JSON.parse(val) : [];
mQueue = queue;
resolve(mQueue);
});
}
});
}
// Set queue
function setQueue(queue) {
return new Promise(function(resolve, reject) {
// Store queue to memory..
mQueue = queue;
// Write to indexed db..
localforage.setItem("queue", mQueue)
.then(function(){
resolve(mQueue);
});
});
}
// Do something..
function doSomething(queuedData) {
return new Promise(function(resolve, reject) {
// just print something and resolve
console.log(queuedData);
resolve();
});
}
我的问题的简短版本 - 考虑到我的特定 use-case - 是:我如何同步处理推送消息而不必使用更多异步 API?
如果我将这些问题分成多个:
- 我认为我需要 queue 那些消息是否正确?
- 如果是这样,如何处理 SW 中的 queues?
- 我不能(完全)依赖全局变量,因为 SW 可能会被杀死,我不能使用 LocalStorage 或类似的同步 API,所以我需要使用另一个异步 API 喜欢 IndexedDB 来做这个。这个假设是否正确?
- 我上面的代码是正确的方法吗?
- 有点相关:由于我需要将 event.waitUntil 从承诺传递到承诺,直到 queue 被处理,我在
handleQueue()
中调用resolve(handleQueue())
是否正确保持下去?或者我应该做return handleQueue()
?或者..?
只是为了理解 "why not use collapse_key":这是一个聊天应用程序,每个聊天室都有自己的标签。一个用户可以参与 4 个以上的聊天室,由于 firebase 将 collapse_keys 的数量限制为 4 个,我不能使用它。
所以我要大胆地说,将内容序列化到 IDB 可能有点矫枉过正。只要您等到所有待处理的工作都完成之后再解决传递给 event.waitUntil()
的承诺,服务工作者就应该保持活动状态。 (如果需要几分钟才能完成这项工作,那么 service worker 无论如何都有可能被杀死,但对于你所描述的情况,我认为这种风险很低。)
下面是我将如何构建您的代码的粗略草图,利用当前支持服务工作者的所有浏览器中的本机 async
/await
支持。
(我还没有实际测试过这些,但从概念上讲我认为它是合理的。)
// In your service-worker.js:
const isPushMessageHandlerRunning = false;
const queue = [];
self.addEventListener('push', event => {
var data = event.data.json().data;
event.waitUntil(queueData(data));
});
async function queueData(data) {
queue.push(data);
if (!isPushMessageHandlerRunning) {
await handlePushDataQueue();
}
}
async function handlePushDataQueue() {
isPushMessageHandlerRunning = true;
let data;
while(data = queue.shift()) {
// Await on something asynchronous, based on data.
// e.g. showNotification(), getNotifications() + notification.close(), etc.
await ...;
}
isPushMessageHandlerRunning = false;
}