我一直在使用chrome-promise库来包含Chrome扩展API,其外观返回promises而不是使用回调。这通常很有效,但我似乎遇到了chrome.storage.local
API的问题。
我的扩展程序的事件页面会侦听chrome.tabs.onActivated
和chrome.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
中获取和设置数据在下一个启动之前完成。另一个将从事件中创建的整个承诺链排队,并且在当前事件完全完成之前不会启动下一个。我不确定哪个更好,但我认为锁定时间越短越好。
欢迎任何建议或更好的答案。
答案 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();