在Chrome扩展程序中,如何使用chrome-promise确保之前的承诺在下一个承诺之前解决?

时间:2017-11-09 04:29:34

标签: javascript google-chrome google-chrome-extension concurrency promise

我一直在使用chrome-promise库来包含Chrome扩展API,其外观返回promises而不是使用回调。这通常很有效,但我似乎遇到了chrome.storage.local API的问题。

我的扩展程序的事件页面会侦听chrome.tabs.onActivatedchrome.tabs.onRemoved个事件。当它获得onActivated事件时,它会将选项卡信息添加到数组并调用chrome.storage.local.set(data)以将更新的数组存储在本地存储中。

当它获得onRemoved事件时,它调用chromepromise.storage.local.get(null).then(...)通过promise获取选项卡列表,从数组中删除选项卡信息,然后再次调用chrome.storage.local.set()以保存更新的数组。

问题是onActivated事件似乎在来自onRemoved事件的promise流程解决之前触发。因此onActivated处理程序检索旧的存储数组,关闭的选项卡仍在其中,然后按下新激活的选项卡。因此,存储的标签数据现在包括已经关闭的标签。

我认为这是使用promises而不是回调的问题,但我想知道是否有其他人遇到过这个库的问题而且解决了这个问题。

更新

作为wOxxOm points out,这是一个通用的问题,"仲裁对单个资源的不可预测的异步访问,例如chrome.storage"而不是chrome-promise库的唯一。

经过一番研究,我想出了几个解决方案,下面给出了答案。一个使用互斥锁来确保(我认为)一个承诺链在chrome.storage中获取和设置数据在下一个启动之前完成。另一个将从事件中创建的整个承诺链排队,并且在当前事件完全完成之前不会启动下一个。我不确定哪个更好,但我认为锁定时间越短越好。

欢迎任何建议或更好的答案。

2 个答案:

答案 0 :(得分:0)

互斥

更新:我最终使用以下方法创建module,使用互斥锁确保Chrome扩展存储的获取和设置维持其顺序。到目前为止似乎运作良好。

此解决方案使用this article中的互斥锁实现。 addTab()removeTab()使用可执行所有存储获取和设置的功能调用storageMutex.synchronize()。这可以防止以后的事件影响早期事件的存储。

以下代码是扩展程序的一个非常简化的版本,但它确实运行。底部的playNextEvent()调用模拟打开4个选项卡,切换回选项卡2并关闭它,然后激活选项卡3。使用setTimeout()以便一切都不会作为一个长调用堆栈运行。

function Mutex() {
    this._busy  = false;
    this._queue = [];
}

Object.assign(Mutex.prototype, {
    synchronize: function(task) {
        var self = this;

        return new Promise(function(resolve, reject) {
            self._queue.push([task, resolve, reject]);
            if (!self._busy) {
                self._dequeue();
            }
        });
    },

    _dequeue: function() {
        var next = this._queue.shift();

        if (next) {
            this._busy = true;
            this._execute(next);
        } else {
            this._busy = false;
        }
    },

    _execute: function(record) {
        var task = record[0],
            resolve = record[1],
            reject = record[2],
            self = this;

        task().then(resolve, reject).then(function() {
            self._dequeue();
        });
    }
});


const storageMutex = new Mutex();


function onActivated(tabID) {
    console.log("EVENT onActivated", tabID);
    return Promise.resolve(tabID).then(tab => addTab(tab));
}

function onRemoved(tabID) {
    console.log("EVENT onRemoved", tabID);
    return removeTab(tabID);
}


var localData = {
        tabs: []
    };

function delay(time) {
   return new Promise(resolve => setTimeout(resolve, time));
}

function getData()
{
    return delay(0).then(() => JSON.parse(JSON.stringify(localData)));
}

function saveData(data, source)
{
    return delay(0)
        .then(() => {
            localData = data;
            console.log("save from:", source, "localData:", localData);
            return Promise.resolve(localData);
        });
}

function addTab(tabID)
{
    return storageMutex.synchronize(() => getData().then((data) => {
        console.log("addTab", tabID, "data:", data);
        data.tabs = data.tabs.filter(tab => tab != tabID);
        data.tabs.push(tabID);
        return saveData(data, "addTab");
    }));
}

function removeTab(tabID)
{
    return storageMutex.synchronize(() => getData().then((data) => {
        console.log("removeTab", tabID, "data:", data);
        data.tabs = data.tabs.filter(tab => tab != tabID);
        return saveData(data, "removeTab");
    }));
}


const events = [
    () => onActivated(1),
    () => onActivated(2),
    () => onActivated(3),
    () => onActivated(4),
    () => onActivated(2),
    () => { onRemoved(2); onActivated(3) }
];

function playNextEvent()
{
    var event = events.shift();

    if (event) {
        delay(0).then(() => { event(); delay(0).then(playNextEvent) });
    }
}

playNextEvent();

答案 1 :(得分:0)

队列

此解决方案使用非常简单的排队机制。事件处理程序使用一个函数调用queue(),该函数启动promise链来处理该事件。如果队列中还没有承诺,则立即调用该函数。否则,它会被推入队列,并在当前的保证链完成时被触发。这意味着一次只能处理一个事件,这可能效率不高。



var taskQueue = [];

function queue(
    fn)
{
    taskQueue.push(fn);
    processQueue();
}

function processQueue()
{
    const nextTask = taskQueue[0];

    if (nextTask && !(nextTask instanceof Promise)) {
        taskQueue[0] = nextTask()
            .then((result) => {
                console.log("RESULT", result);
                taskQueue.shift();
                processQueue();
        });
    }
}


function onActivated(tabID) {
    console.log("EVENT onActivated", tabID);
    queue(() => Promise.resolve(tabID).then(tab => addTab(tab)));
}

function onRemoved(tabID) {
    console.log("EVENT onRemoved", tabID);
    queue(() => removeTab(tabID));
}


var localData = {
        tabs: []
    };

function delay(time) {
   return new Promise(resolve => setTimeout(resolve, time));
}

function getData()
{
    return delay(0).then(() => JSON.parse(JSON.stringify(localData)));
}

function saveData(data, source)
{
    return delay(0)
        .then(() => {
            localData = data;
            console.log("save from:", source, "localData:", localData);
            return Promise.resolve(localData);
        });
}

function addTab(tabID)
{
    return getData().then((data) => {
        console.log("addTab", tabID, "data:", data);
        data.tabs = data.tabs.filter(tab => tab != tabID);
        data.tabs.push(tabID);
        return saveData(data, "addTab");
    });
}

function removeTab(tabID)
{
    return getData().then((data) => {
        console.log("removeTab", tabID, "data:", data);
        data.tabs = data.tabs.filter(tab => tab != tabID);
        return saveData(data, "removeTab");
    });
}


const events = [
    () => onActivated(1),
    () => onActivated(2),
    () => onActivated(3),
    () => onActivated(4),
    () => onActivated(2),
    () => { onRemoved(2); onActivated(3) }
];

function playNextEvent()
{
    var event = events.shift();

    if (event) {
        delay(0).then(() => { event(); delay(0).then(playNextEvent) });
    }
}

playNextEvent();