来自browser.runtime.sendMessage()的承诺在异步调用sendResponse()之前完成

时间:2016-11-23 19:44:24

标签: javascript firefox-addon firefox-webextensions

我制作了一个与可执行文件交互的Firefox WebExtension(使用runtime.connectNative)。

以下是扩展的清单:

// manifest.json file

{
  "name": "My Extension",
  "short_name": "myext",
  "version": "2.7",
  "manifest_version": 2,
  "background": {
    "scripts": ["background-script.js"],
    "persistent": true
    },
    "externally_connectable": {
      "ids": ["*"],
      "http://localhost/*"]
  },
   "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content-script.js"]
    }
  ],
  "applications": {
    "gecko": {
      "id": "myext@myext.mysite.com",
      "strict_min_version": "50.0.0"
    }
  },
  "icons": {
    "16": "icon-16.png",
    "32": "icon-32.png",
    "128": "icon-128.png"
  },
  "permissions": [
       "nativeMessaging",
       "webRequest"
    ]
}

以下是内容脚本:

// content-script.js

window.addEventListener("message", function(event) {
  if (event.source == window &&
      event.data.direction &&
      event.data.direction == "from-page-script") {
    alert("Content script received message: \"" + JSON.stringify(event.data.message) + "\"");

    var sending = browser.runtime.sendMessage(event.data.message);

    sending.then(handleResponse, handleError);

  }
});

function handleResponse(message) 
{
  console.log("content-script : handleResponse : message = " + JSON.stringify(message));
  window.wrappedJSObject.foo.p = "OK : " + JSON.stringify(message);
}

function handleError(error) {
  console.log("Message from the background script : " + error.message);
  window.wrappedJSObject.foo.p = "error";
}

这是我的后台脚本:

// background-script.js

var port;

browser.runtime.onMessage.addListener(function(request, sender, sendResponse) 
{
    port = browser.runtime.connectNative("ping_pong");
    var result = port.postMessage(request);

    port.onMessage.addListener(function(response)
    {
        console.log("Received: " + JSON.stringify(response));
        sendResponse(response);
    });
});

我对后台脚本中的以下行有一个问题:

sendResponse(response);

在后台脚本的handleResponse()之前调用内容脚本的方法sendResponse()

因此,当可执行文件花费较长时间执行操作时,可执行文件的结果不会发送到内容脚本,并且内容脚本会收到未定义的结果。

是否有另一种方法可以将可执行文件的结果从后台脚本发送到内容脚本?或者以另一种方式使用回调?

1 个答案:

答案 0 :(得分:3)

使用Promises会引起一些混乱。正在使用无参数实现Promise调用handleResponse(),因为您在不调用runtime.onMessage的情况下退出后台脚本的sendResponse()侦听器。如果调用了sendResponse(),那么参数将是消息 1 。因此,您需要调整.then()以区分不接收任何参数(sendResponse()未调用)和包含响应消息的参数(sendResponse()被调用)。如果我没记错,如果您使用chrome.runtime.sendMessage(),则除非调用responseCallback,否则不会调用sendResponse()函数。

MDN describes the Promise as

  

如果发件人发送了响应,则响应将作为JSON对象实现。否则它将在没有参数的情况下实现。如果连接到分机时发生错误,则将拒绝承诺并显示错误消息。

要异步调用sendResponse(),您需要return true;监听器中的runtime.onMessage

您正在异步回调中调用sendResponse()。因此,您在调用runtime.onMessage之前退出sendResponse()侦听器。如果您打算这样做,但仍想致电sendResponse(),则需要从true听众处返回runtime.onMessage

关于sendResponse()来自MDN runtime.onMessage

  

此函数返回boolean。如果您希望在事件侦听器返回后调用true,它应该从事件侦听器返回sendResponse

所以你的代码可能是:

browser.runtime.onMessage.addListener(function(request, sender, sendResponse) 
{
    port = browser.runtime.connectNative("ping_pong");
    var result = port.postMessage(request);

    port.onMessage.addListener(function portOnMessageListener(response)
    {
        //sendResponse is only valid once. So, if this is how you want to use it, you need
        //  to remove the listener, or in some other way not call sendResponse twice.
        port.onMessage.removeListener(portOnMessageListener);
        console.log("Received: " + JSON.stringify(response));
        sendResponse(response);
    });
    return true;
});

sendResponse()仅有效一次

sendResponse()功能仅对一条消息有效(即每发送一条消息只能发送一个响应)。如果您希望向内容脚本发送多条消息,则需要以不同的方式设置这样做。

  1. 消息的MDN docs state是“JSON对象”。它们可能意味着它是从{JSON转换而来的Object,用于在邮件传输过程中表示它。