Chrome扩展程序消息传递:未发送响应


151

我正在尝试在内容脚本和扩展名之间传递消息

这是我的内容脚本

chrome.runtime.sendMessage({type: "getUrls"}, function(response) {
  console.log(response)
});

在后台脚本中

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.type == "getUrls"){
      getUrls(request, sender, sendResponse)
    }
});

function getUrls(request, sender, sendResponse){
  var resp = sendResponse;
  $.ajax({
    url: "http://localhost:3000/urls",
    method: 'GET',
    success: function(d){
      resp({urls: d})
    }
  });

}

现在,如果我在getUrls函数中的ajax调用之前发送响应,则响应已成功发送,但是在ajax调用的成功方法中,当我发送响应时它不会发送响应,当我进入调试时,我可以看到该sendResponse函数代码中的端口为null 。


存储对sendResponse参数的引用至关重要。没有它,响应对象将超出范围,无法被调用。感谢您的代码,这些代码提示我要解决自己的问题!
TrickiDicki

也许另一种解决方案是使用Promise将所有内容包装在异步函数中,并为异步方法调用await?
Enrique

Answers:


348

文档中chrome.runtime.onMessage.addListener

当事件侦听器返回时,此函数将变为无效,除非您从事件侦听器返回true表示您希望异步发送响应(这将使消息通道保持对另一端开放,直到调用sendResponse为止)。

因此,您只需要return true;在调用后添加getUrls即可表明您将异步调用响应函数。


这是正确的,我在回答中添加了一种自动执行此操作的方式
Zig Mandel 2014年

62
为此+1。浪费了2天时间来尝试调试此问题,这为我节省了时间。我不敢相信在以下消息传递指南中根本没有提到这一点:developer.chrome.com/extensions/messaging
funforums 2014年

6
我以前显然有过这个问题;回来意识到我已经对此表示赞同。这需要以粗体显示,<blink><marquee>在页面上的某处标记。
Qix-蒙尼卡(Monica)

2
@funforums仅供参考,此行为现在记录在消息传递文档中(区别在于:codereview.chromium.org/1874133002/patch/80001/90002)。
罗伯W

10
我发誓这是我使用过的最不直观的API。
michaelsnowden '16

8

可接受的答案是正确的,我只想添加示例代码以简化此操作。问题在于,API(在我看来)设计得不好,因为它迫使我们开发人员知道是否将异步处理特定消息。如果您处理许多不同的消息,这将成为不可能完成的任务,因为您不知道是否深入了解某些函数的传入sendResponse是否称为异步。考虑一下:

chrome.extension.onMessage.addListener(function (request, sender, sendResponseParam) {
if (request.method == "method1") {
    handleMethod1(sendResponse);
}

我怎么知道handleMethod1电话的深处是否是异步的?进行修改的人如何handleMethod1知道会通过引入异步内容来中断呼叫者?

我的解决方案是这样的:

chrome.extension.onMessage.addListener(function (request, sender, sendResponseParam) {

    var responseStatus = { bCalled: false };

    function sendResponse(obj) {  //dummy wrapper to deal with exceptions and detect async
        try {
            sendResponseParam(obj);
        } catch (e) {
            //error handling
        }
        responseStatus.bCalled= true;
    }

    if (request.method == "method1") {
        handleMethod1(sendResponse);
    }
    else if (request.method == "method2") {
        handleMethod2(sendResponse);
    }
    ...

    if (!responseStatus.bCalled) { //if its set, the call wasn't async, else it is.
        return true;
    }

});

无论您选择如何处理消息,这都会自动处理返回值。请注意,这假设您永远不会忘记调用响应函数。还请注意,铬可能为我们实现了自动化,我不明白为什么他们没有这样做。


一个问题是,有时您不希望调用response函数,在这种情况下,您应该返回false。否则,您将阻止Chrome释放与邮件相关的资源。
rsanchez 2014年

是的,这就是为什么我说不要忘记调用回调函数。可以通过约定处理程序(handleMethod1等)返回false来表示“无响应”的情况来处理这种特殊情况(尽管Id总是总是做出响应,甚至是空的)。这样,可维护性问题仅局限于那些特殊的“不退货”情况。
Zig Mandel 2014年

8
不要重新发明轮子。不推荐使用的chrome.extension.onRequest/ chrome.exension.sendRequest方法的行为与您描述的完全相同。不赞成使用这些方法,因为事实证明,许多扩展开发人员并未关闭消息端口。当前的API(要求return true)是一种更好的设计,因为硬失败比静默泄漏更好。
罗布W

@RobW但是问题是什么?我的回答可以防止开发人员忘记返回true。
Zig Mandel

@ZigMandel如果要发送响应,请使用return true;。如果异步调用仍在正确处理中,则在同步调用时不会阻止清理端口。此答案中的代码引入了不必要的复杂性,没有明显的好处。
罗布W

2

您可以使用我的库https://github.com/lawlietmester/webextension使此功能在带有Firefox的Chrome和FF中都可以正常工作,而无需回调。

您的代码如下所示:

Browser.runtime.onMessage.addListener( request => new Promise( resolve => {
    if( !request || typeof request !== 'object' || request.type !== "getUrls" ) return;

    $.ajax({
        'url': "http://localhost:3000/urls",
        'method': 'GET'
    }).then( urls => { resolve({ urls }); });
}) );
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.