服务工作者:如何进行同步队列?

时间:2017-04-10 07:08:53

标签: javascript service-worker

我有一个Service Worker,它接收来自Firebase FCM的推送消息。它们会导致显示或取消通知。用户可以拥有多个设备(取消的目的是什么:当用户已经对通知A采取行动时,我试图在所有设备上将其解除)。

我遇到的问题是其中一个用户的设备处于脱机状态或完全关闭。设备上线后,firebase会提供之前无法提供的所有消息。所以,例如,你得到:

  • 显示含有内容X的通知A
  • 显示含有内容Y的通知A(替换通知A)
  • 显示内容为Z的通知B
  • 取消通知A

SW迅速接收这些消息。问题是取消通知比显示通知要快得多(~2ms vs 16ms)。因此,在第一条(或第二条)消息实际创建通知之前处理第4条消息,结果是通知未被取消。

//编辑:下面的重大编辑问题。添加了示例代码并打破了我的问题。还编辑了标题以更好地反映我的实际基本问题。

我尝试将消息推送到队列中并逐个处理它们。事实证明这可能会变得有点复杂,因为SW中的所有内容都是异步的,更糟糕​​的是,当浏览器认为SW完成其工作时,它可以随时被杀死。我试图以持久的方式存储队列,但由于LocalStorage在SW中不可用,我需要使用异步IndexedDB API。更多可能导致问题的异步调用(例如丢失项目)。

event.waitUntil也可能认为我的工作人员在实际完成之前就完成了,因为我没有正确地通过火炬传递。从承诺到承诺......

这是我尝试过的(很多)简化代码:

// 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();
    });
}

我的问题的简短版本 - 考虑到我的特定用例 - 是:如何在不使用更多异步API的情况下同步处理推送消息?

如果我将这些问题分成多个:

  • 我是否正确地假设我需要排队这些消息?
  • 如果是这样,如何处理SW中的队列?
  • 我无法(完全)依赖全局变量,因为SW可能会被杀死而我无法使用LocalStorage或类似的同步API,因此我需要使用另一个异步API IndexedDB这样做。这个假设是否正确?
  • 我的代码是否在正确的方法之上?
  • 有点相关:因为我需要将event.waitUntil从promise传递到promise,直到处理完队列,我是否可以在resolve(handleQueue())内调用handleQueue()来保持它的运行状态?或者我应该return handleQueue()吗?还是......?

只是为了理解&#34;为什么不使用collapse_key&#34;:它是一个聊天应用程序,每个聊天室都有它自己的标签。用户可以参与4个以上的聊天室,因为firebase将collapse_keys的数量限制为4,我无法使用它。

1 个答案:

答案 0 :(得分:2)

因此,我要走出困境,并说向IDB序列化事情可能有点过分。只要您在解决传递给event.waitUntil()的承诺之前等待所有待处理的工作完成,服务工作者就应该保持活跃状态​​。 (如果完成这项工作需要几分钟,那么服务工作者无论如何都有可能被杀,但对于你描述的内容,我说这种风险很低。)

以下是我如何构建代码的粗略草图,利用当前支持服务工作者的所有浏览器中的原生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;
}