Chrome Extension Message Passing: Antwort nicht gesendet

151

Ich versuche, Nachrichten zwischen dem Inhaltsskript und der Erweiterung zu übergeben

Folgendes habe ich im Content-Skript

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

Und im Hintergrundskript habe ich

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})
    }
  });

}

Wenn ich nun die Antwort vor dem Ajax-Aufruf in der getUrlsFunktion sende , wird die Antwort erfolgreich gesendet, aber in der Erfolgsmethode des Ajax-Aufrufs, wenn ich die Antwort sende, wird sie nicht gesendet. Wenn ich mit dem Debuggen beginne, kann ich das sehen Der Port ist innerhalb des sendResponseFunktionscodes null .

Ein Gebot
quelle
Das Speichern eines Verweises auf den Parameter sendResponse ist wichtig. Ohne sie verlässt das Antwortobjekt den Gültigkeitsbereich und kann nicht aufgerufen werden. Vielen Dank für den Code, der mich dazu veranlasst hat, mein Problem zu beheben!
TrickiDicki
Vielleicht besteht eine andere Lösung darin, alles in eine asynchrone Funktion mit Promise zu verpacken und auf die asynchronen Methoden zu warten.
Enrique

Antworten:

348

Aus der Dokumentation fürchrome.runtime.onMessage.addListener :

Diese Funktion wird ungültig, wenn der Ereignis-Listener zurückkehrt, es sei denn, Sie geben vom Ereignis-Listener true zurück, um anzugeben, dass Sie eine Antwort asynchron senden möchten (dies hält den Nachrichtenkanal bis zum Aufruf von sendResponse für das andere Ende offen).

Sie müssen also nur return true;nach dem Aufruf hinzufügen , getUrlsum anzuzeigen, dass Sie die Antwortfunktion asynchron aufrufen.

rsanchez
quelle
das ist richtig, ich habe eine Möglichkeit hinzugefügt, dies in meiner Antwort zu automatisieren
Zig Mandel
62
+1 dafür. Es hat mich gerettet, nachdem ich 2 Tage damit verbracht hatte, dieses Problem zu beheben. Ich kann nicht glauben, dass dies überhaupt nicht in der Anleitung zur Nachrichtenübermittlung erwähnt wird: developer.chrome.com/extensions/messaging
funforums
6
Ich hatte dieses Problem anscheinend schon einmal. kam zurück, um zu erkennen, dass ich dies bereits positiv bewertet hatte. Dies muss fett gedruckt <blink>und <marquee>irgendwo auf der Seite markiert sein.
Qix - MONICA wurde
2
@funforums FYI, dieses Verhalten ist jetzt in der Messaging- Dokumentation dokumentiert (der Unterschied ist hier: codereview.chromium.org/1874133002/patch/80001/90002 ).
Rob W
10
Ich schwöre, dies ist die unintuitivste API, die ich je verwendet habe.
michaelsnowden
8

Die akzeptierte Antwort ist richtig. Ich wollte nur Beispielcode hinzufügen, der dies vereinfacht. Das Problem ist, dass die API (meiner Ansicht nach) nicht gut gestaltet ist, da sie uns Entwickler dazu zwingt, zu wissen, ob eine bestimmte Nachricht asynchron behandelt wird oder nicht. Wenn Sie viele verschiedene Nachrichten verarbeiten, wird dies zu einer unmöglichen Aufgabe, da Sie nie wissen, ob eine übergebene sendResponse in einer bestimmten Funktion als asynchron bezeichnet wird oder nicht. Bedenken Sie:

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

Wie kann ich wissen, ob handleMethod1der Anruf tief im Inneren asynchron ist oder nicht? Wie kann jemand, der Änderungen handleMethod1vornimmt, wissen, dass er einen Anrufer durch die Einführung von etwas Asynchronem unterbricht?

Meine Lösung lautet:

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;
    }

});

Dadurch wird der Rückgabewert automatisch verarbeitet, unabhängig davon, wie Sie die Nachricht behandeln. Beachten Sie, dass dies voraussetzt, dass Sie nie vergessen, die Antwortfunktion aufzurufen. Beachten Sie auch, dass Chrom dies für uns hätte automatisieren können. Ich verstehe nicht, warum dies nicht der Fall war.

Zig Mandel
quelle
Ein Problem ist, dass Sie manchmal die Antwortfunktion nicht aufrufen möchten und in diesen Fällen false zurückgeben sollten . Wenn Sie dies nicht tun, verhindern Sie, dass Chrome Ressourcen freigibt, die der Nachricht zugeordnet sind.
Rsanchez
Ja, deshalb habe ich gesagt, ich soll den Rückruf nicht vergessen. Dieser spezielle Fall, den Sie ansprechen, kann durch eine Konvention behandelt werden, dass der Handler (handleMethod1 usw.) false zurückgibt, um den Fall "keine Antwort" anzuzeigen (obwohl Id eher immer eine Antwort gibt, auch eine leere). Auf diese Weise wird das Wartbarkeitsproblem nur auf diese speziellen Fälle ohne Rückgabe beschränkt.
Zig Mandel
8
Erfinde das Rad nicht neu. Die veralteten chrome.extension.onRequest/ chrome.exension.sendRequestMethoden verhalten sich genau so, wie Sie es beschreiben. Diese Methoden sind veraltet, da sich herausstellt, dass viele Erweiterungsentwickler den Nachrichtenport NICHT geschlossen haben. Die aktuelle API (erforderlich return true) ist ein besseres Design, da ein hartes Versagen besser ist als ein leises Auslaufen.
Rob W
@RobW aber was ist das Problem dann? Meine Antwort verhindert, dass der Entwickler vergisst, true zurückzugeben.
Zig Mandel
@ZigMandel Wenn Sie eine Antwort senden möchten, verwenden Sie einfach return true;. Es verhindert nicht, dass der Port bereinigt wird, wenn der Anruf synchronisiert wird, während asynchrone Anrufe weiterhin korrekt verarbeitet werden. Der Code in dieser Antwort führt zu unnötiger Komplexität ohne offensichtlichen Nutzen.
Rob W
2

Sie können meine Bibliothek https://github.com/lawlietmester/webextension verwenden , damit dies sowohl in Chrome als auch in FF mit Firefox ohne Rückruf funktioniert.

Ihr Code sieht folgendermaßen aus:

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 }); });
}) );
Rustam
quelle