ManifestV3 new promise error: The message port closed before a response was received

ManifestV3 new promise error: The message port closed before a response was received

我正在开发一个扩展,它在其内容脚本和后台服务工作者(清单 V3)之间进行大量消息传递,我注意到新的基于 Promise 的 V3 有一个奇怪的问题 APIs,特别是 sendResponse() 函数。

对于需要响应的 API 调用,一切正常。但是如果我不需要响应并且不提供回调函数或使用 promise 的 .then() 方法(或 async/await),则会抛出一个 Promise 错误 - 它说“消息端口在 a 之前关闭已收到回复。"

奇怪的是,调用仍然有效,所以我猜这个错误更像是一个警告。

代码示例:

在内容脚本中,向后台发送消息:

chrome.runtime.sendMessage({ type: 'toggle_setting' })

后台脚本获取消息并执行某些操作,然后在不发送响应的情况下退出:

chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => {
  if (message.type === 'toggle-setting') {
    //* do whatever it does
  }
})

该后台代码引发了上述错误。但是,如果我向其中添加一行并调用不带参数的 sendResponse() 函数,则不会发生错误。

chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => {
  sendResponse()
  if (message.type === 'toggle-setting') {
    //* do whatever it does
  }
})

所以这消除了错误消息,但我不太清楚为什么在不需要或不需要响应时有必要这样做。是否有其他方式向基于 Promise 的 V3 API 发送信号,或者现在是否有必要调用 sendResponse(),即使您不需要?

在消息侦听器的末尾添加一个 return true 以允许异步响应

是一个bug in Chrome 99-101,固定在Chrome102.

The reason is that sendMessage is now promisified internally, so we can await it, but the byproduct is that when we don't specify a callback ourselves, it is added internally anyway in order for the call to return a Promise, which means that since we don't call sendResponse in onMessage, the API will think that it was us who made a mistake of using a callback and not providing a response, and report it as such.

解决方法 1:在 chrome.runtime.onMessage

中调用 sendResponse()
  • 发件人(在问题中是内容脚本):

    chrome.runtime.sendMessage('test');
    
  • receiver(题中是后台脚本):

    chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
      doSomethingWithoutResponding(msg);
      sendResponse();
    });
    

解决方法 2:抑制此特定错误

还修复了 API 在调用之前固有的调用堆栈缺失问题。

// sender (in the question it's the content script)
sendMessage('foo');

// sendMessage('foo').then(res => whatever(res));
// await sendMessage('foo');

function sendMessage(msg) {
  const err1 = new Error('Callstack before sendMessage:');
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(msg, res => {
      let err2 = chrome.runtime.lastError;
      if (!err2 || err2.message.startsWith('The message port closed before')) {
        resolve(res);
      } else {
        err2 = new Error(err2.message);
        err2.stack += err1.stack.replace(/^Error:\s*/, '');
        reject(err2);
      }
    });
  });
}