Mozilla WebExtension API 存储 - 使用和不使用断点进行调试会导致不同的输出

Mozilla WebExtension API storage - Debugging with and without breakpoints leads to different output


大家好,
我正在尝试为 Mozilla-Firefox 浏览器实现一个附加组件。 以下脚本显示了我已经成功集成的一个后台脚本。它使用 Mozilla WebExtension API 存储。它被执行,但浏览器控制台上的日志让我感到惊讶。 我交替记录任何内容和:

bla
bla
bla

当且仅当我在调试模式下的代码的重要行(尤其是最后 5 行)上设置断点时,我总能得到预期的结果:

bla

输出如何取决于设置断点。 我不知道发生了什么,也无法在互联网上找到任何类似的问题。有人可以向我解释发生了什么以及我可以做些什么来防止错误输出吗?

storeManager.js:

var localStore = chrome.storage.local;  
var StoreManager = function(){};

StoreManager.prototype.addItem = function(type, item){
    var thisRef = this;

    localStore.get(type, 
        function(obj){
            if(chrome.runtime.lasterror){
                console.log(chrome.runtime.lastError);
            }else{
                var oldArr = obj[type];

                if(oldArr === undefined)
                    oldArr = [];

                if(oldArr.indexOf(item) !== -1)
                    return;

                oldArr.push(item);
                thisRef.setItem(type, oldArr);
            }
        }
    );
}

StoreManager.prototype.removeItem = function(type, item){
    var thisRef = this;

    localStore.get(type, 
        function(obj){
            if(chrome.runtime.lasterror){
                console.log(chrome.runtime.lastError);
            }else{
                var oldArr = obj[type];

                if(oldArr === undefined)
                    return;

                var index = oldArr.indexOf(item);
                if(index === -1)
                    return;

                oldArr.splice(index, 1);
                thisRef.setItem(type, oldArr);
            }   
        }
    );
}

StoreManager.prototype.setItem = function(type, item){
    localStore.set({ [type] : item }, 
        function(){
            if(chrome.runtime.lasterror){
                console.log(chrome.runtime.lastError);
            }
        }
    );
}

StoreManager.prototype.visitItems = function(type, visit){
    localStore.get(type, 
        function(obj){
            if(chrome.runtime.lasterror){
                console.log(chrome.runtime.lastError);
            }else{
                var oldArr = obj[type];

                if(oldArr !== undefined){
                    for(var i=0; i<oldArr.length; i++){
                        visit(oldArr[i]);
                    }
                }
            }
        }
    );
}

function justLog(str){
    console.log(str);
}

var sm = new StoreManager();
sm.visitItems("lol", justLog);
sm.addItem("lol", "bla");
sm.visitItems("lol", justLog);
sm.removeItem("lol", "bla");
sm.visitItems("lol", justLog);


manifest.json:

{
    "manifest_version": 2,
    "name": "Addon",
    "version": "0.0",

    "description": "no desc",

    "background": {
        "scripts": [
            "storeManager.js"
        ]
    },

    "permissions": [
        "storage"
    ]
}

storage.local.set() is asynchronous. The data isn't guaranteed to be visible to a call to get() 直到回调被调用(或者如果你使用 browser.storage.localset() 将 return 一个 Promise 并且当那个时候数据将被保证写入承诺解决)。

您的代码本身就充满了竞争条件。对 addItem()removeItem() 的调用正在与对 visitItems() 的调用竞争,通过设置断点,您可以让一条路径或另一条路径 "win" 竞争。

如果您期望确定性结果,我建议使用 storage.local 的 Promise 变体并将您的代码编写为一系列链式处理程序。