1

我有一个引导扩展,它与 Firefox 的 chrome 部分交互(即甚至在内容加载之前),并且需要查询 SQLite 数据库进行一些检查。我更喜欢同步通话。但是,由于同步调用在性能方面很差并且可能导致可能的 UI 问题,因此我需要进行异步 DB 调用。

我的用例是这样的:

  • 对数据库进行aysnc调用
  • 完成后做进一步处理

现在,这可以通过在handleCompletion部分executeAsync功能中放置“进一步处理”部分来轻松处理。

但是,我希望无论是否执行此语句都可以完成“进一步处理”,即此数据库查找可能发生也可能不发生。如果它没有很好地发生,那就继续吧。如果是,我需要等待。所以,我正在使用基于标志的策略;handleCompletionCalled我在handleError&handleCompletion回调中设置了一个标志true

在进一步处理部分,我做了一个

while(handleCompletionCalled) {
 // do nothing
}

//further processing

这是一个好策略还是我可以做得更好(我真的不想为此使用观察者等,因为我的整个扩展中有很多这样的情况,我的代码将充满观察者)?

4

1 回答 1

2

使用while循环等待是一个非常糟糕的主意™。如果这样做,结果将是您挂起 UI,或者至少通过尽可能快地多次运行循环来将 CPU 使用率推高至顶峰。1

关于异步编程的要点是,您启动一​​个动作,然后在活动完成或失败后执行另一个函数,即回调。这要么允许您启动多个操作,要么将处理放弃给整个代码的其他部分。通常,此回调应处理依赖于异步操作完成的所有活动。回调函数本身不必包含执行其他处理的代码。在它完成了响应异步操作完成需要发生的事情之后,它可以调用另一个函数,例如doOtherProcessing().

如果您启动多个异步操作,则可以通过为每个任务设置标志和在所有不同回调函数结束时调用的单个函数来等待所有操作完成,例如:

function continueAfterAllDone(){
    if(task1Done && task2Done && task3Done && task4Done) {
        //do more processing
    }else{
        //Not done with everything, yet.
        return;
    }
}

这可以通过使用数组或任务队列扩展到任意数量的任务,然后该函数检查所有这些任务是否已完成,而不是一组硬编码的任务。

等待:
如果您要执行另一个处理路径,但必须等待异步操作的完成,您应该通过设置计时器或间隔来执行等待。然后您让处理器在指定的时间段内让出,直到您再次检查以查看您需要继续的条件是否已经发生。

在引导式附加组件中,您可能需要使用nsITimer接口来实现超时或间隔计时器。这是必需的,因为在您运行初始化代码时,可能不<window>存在(即可能无法访问 a window.setTimeout())。

如果您要实现等待其他任务,您可以执行以下操作:

const Cc = Components.classes;
const Ci = Components.interfaces;

var asyncTaskIsDone = false;
var otherProcessingDone = false;
// Define the timer here in case we want to cancel it somewhere else.
var taskTimeoutTimer;

function doStuffSpecificToResultsOfAsyncAction(){
    //Do the other things specific to the Async action callback.
    asyncTaskIsDone = true;
    //Can either call doStuffAfterOtherTaskCompletesOrInterval() here, 
    //  or wait for the timer to fire.
    doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}

function doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval(){
    if(asyncTaskIsDone && otherProcessingDone){
        if(typeof taskTimeoutTimer.cancel === "function") {
            taskTimeoutTimer.cancel();
        }
        //The task is done
    }else{
        //Tasks not done.
        if(taskTimeoutTimer){
            //The timer expired. Choose to either continue without one of the tasks
            //  being done, or set the timer again.
        }
        //}else{ //Use else if you don't want to keep waiting.
        taskTimeoutTimer = setTimer(doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval
                                    ,5000,false)
        //}
    }
}

function setTimer(callback,delay,isInterval){
    //Set up the timeout (.TYPE_ONE_SHOT) or interval (.TYPE_REPEATING_SLACK).
    let type = Ci.nsITimer.TYPE_ONE_SHOT
    if(isInterval){
        type = Ci.nsITimer.TYPE_REPEATING_SLACK
    }
    let timerCallback = {
        notify: function notify() { 
            callback();
        }
    }
    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.initWithCallback(timerCallback,delay,type);
    return timer;
}

function main(){
   //Launch whatever the asynchronous action is that you are doing.
   //The callback for that action is doStuffSpecificToResultsOfAsyncAction().

    //Do 'other processing' which can be done without results from async task here.

    otherProcessingDone = true;
    doStuffAfterBothAsyncAndOtherTaskCompletesOrInterval();
}

Firefox 启动时的初始化代码:
上面的代码是根据我用来延迟一些启动操作的修改的,这些操作不必Firefox UI 显示之前完成。

在我的一个附加组件中,我有一个合理的处理量应该完成,但这对于向用户显示 Firefox UI并不是绝对必要的。[请参阅“扩展中的性能最佳实践”。]因此,为了不延迟 UI,我使用了一个计时器和一个在 Firefox 启动 5 秒后执行的回调。这使 Firefox UI 对用户的响应更加灵敏。代码是:

const Cc = Components.classes;
const Ci = Components.interfaces;

// Define the timer here in case we want to cancel it somewhere else.
var startupLaterTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

function startupLater(){
  //Tasks that should be done at startup, but which do not _NEED_ to be
  //  done prior to the Firefox UI being shown to the user.
}

function mainStartup(){
   let timerCallback = {
        notify: function notify() { 
            startupLater();
        }
    }
    startupLaterTimer = startupLaterTimer.initWithCallback(timerCallback,5000
                                                           ,Ci.nsITimer.TYPE_ONE_SHOT);
}

请注意,所做的startupLater()不一定包括在用户第一次激活广告之前所需的一切。就我而言,这是在用户按下插件的 UI 按钮或通过上下文菜单调用它之前必须完成的所有事情。超时可能/应该更长(例如 10 秒),但是是 5 秒,所以我在开发时不必等待这么长时间进行测试。请注意,还有一次性/启动任务只能在用户按下插件的 UI 按钮后才能完成。

1. 这里的一般编程问题:在某些编程语言中,如果您从不从主代码中让出处理器,则您的回调可能永远不会被调用。在这种情况下,您只会锁定在while循环中并且永远不会退出。

于 2016-08-09T16:45:45.173 回答