在执行另一个事件处理程序之前等待一个事件处理程序

Waiting for one event handler before executing another one

我正在编写一个 Firefox 浏览器扩展程序,但我一直在思考如何在从后台脚本发送消息之前等待内容脚本加载。 这是我要实现的顺序:

  1. 用户点击上下文菜单项(点击处理程序在后台脚本中)
  2. 后台脚本创建新标签
  3. 内容脚本在新标签页中完全加载
  4. 后台脚本向内容脚本发送消息(带数据)
  5. 内容脚本使用数据

显然,第 4 步需要加载内容脚本;否则,不会收到消息。

看了之前类似的问题,大部分答案都不对(他们把事件监听方法包装在一个Promise中,要么导致监听太多,要么Promise太少),或者似乎不适用于我的场景(这些答案完全通过将一个回调放在另一个回调中来绕过问题;这在这里行不通)。

到目前为止,我所做的尝试是让内容脚本在准备就绪时发送一条消息,这很有效,但我仍然不确定如何让点击处理程序(从第 1 步开始)等待消息来自内容脚本(假设步骤 3.5)。 据我所知,我假设我必须在点击处理程序之外定义消息处理程序,除非有一种方法可以在点击处理程序中接收消息。

这是我当前的代码作为最小工作示例:

background.js:

let ports = {
    '1': null,
    '2': null
};

xyz = () => { /*...*/ }
tabHasLoaded = () => { /*...*/ }

browser.runtime.onConnect.addListener(connectHandler);
connectHandler = (p) => {
    ports[p.name] = p;
    switch (p.name) {
        case '1':
            ports['1'].addListener(xyz);
            break;
        case '2':
            ports['2'].addListener(tabHasLoaded);
            break;
    }
};

browser.contextMenus.onClicked.addListener((info, tab) => {
    let data, uri;
    //...
    browser.tabs.create({
        url: uri
    }).then((tab) => {
        // need to wait for tabHasLoaded() to get called
        ports['2'].postMessage({
            msg: data
        })
    });
});

1.js(其他内容脚本):

let myPort = browser.runtime.connect({
    name: '1'
});
document.addEventListener("click", (e) => {
    myPort.postMessage({
        msg: e.target.id
    });
});

2.js(新标签的内容脚本,点击上下文菜单后):

let myPort = browser.runtime.connect({
    name: '2'
});
myPort.postMessage({
    msg: "READY" // tabHasLoaded() should now get called in background.js
});
myPort.onMessage.addListener((msg) => {
    // waiting for background.js to send me data
});

有没有理想的方法来处理这个问题?

我仍然认为承诺是可行的方法...

更新

更改代码以使用您的 MWE...请注意,这是 untested/not-optimized 代码只是为了概述这个想法...它应该看起来像这样:

background.js

let ports = {
    '1': null,
    '2': null
};

xyz = () => { /*...*/ }

browser.runtime.onConnect.addListener(connectHandler);
connectHandler = (p) => {
    ports[p.name] = p;
    switch (p.name) {
        case '1':
            ports['1'].addListener(xyz);
            break;
    }
};

browser.contextMenus.onClicked.addListener(async (info, tab) => {
    let data, uri;
    //...
    const tab = await LoadAndWaitForPort2(uri)
    ports['2'].postMessage({msg: data})
});

function LoadAndWaitForPort2(uri){
  return new Promise((resolve, reject)=>{
    const tab
    const tabHasLoaded = (evt) => {
      if(evt.data.msg === "READY"){
        ports['2'].removeListener(tabHasLoaded)
        resolve(tab)
      } else {
        reject("error!")
      }   
    }
    ports['2'].addListener(tabHasLoaded)
    tab = await browser.tabs.create({url: uri})
  })
}

2.js

let myPort = browser.runtime.connect({
    name: '2'
});
myPort.postMessage({
    msg: "READY" // tabHasLoaded() should now get called in background.js
});
myPort.onMessage.addListener((msg) => {
    // waiting for background.js to send me data
});