Passaggio messaggio estensione Chrome: risposta non inviata


151

Sto cercando di passare i messaggi tra lo script del contenuto e l'estensione

Ecco cosa ho in content-script

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

E nella sceneggiatura di sfondo che ho

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

}

Ora se invio la risposta prima della chiamata ajax nella getUrlsfunzione, la risposta viene inviata correttamente, ma nel metodo di successo della chiamata ajax quando invio la risposta non la invia, quando vado nel debug posso vedere che la porta è nulla all'interno del codice per la sendResponsefunzione.


La memorizzazione di un riferimento al parametro sendResponse è fondamentale. Senza di esso, l'oggetto risposta non rientra nell'ambito e non può essere chiamato. Grazie per il codice che mi ha suggerito di risolvere il mio problema!
Tricki Dicki,

forse un'altra soluzione è avvolgere tutto all'interno di una funzione asincrona con Promise e chiamare wait per i metodi asincroni?
Enrique,

Risposte:


348

Dalla documentazione perchrome.runtime.onMessage.addListener :

Questa funzione diventa non valida quando il listener di eventi ritorna, a meno che non si restituisca true dal listener di eventi per indicare che si desidera inviare una risposta in modo asincrono (ciò manterrà il canale del messaggio aperto all'altra estremità fino a quando non viene chiamato sendResponse).

Quindi devi solo aggiungere return true;dopo la chiamata getUrlsper indicare che chiamerai la funzione di risposta in modo asincrono.


questo è corretto, ho aggiunto un modo per automatizzare questo nella mia risposta
Zig Mandel

62
+1 per questo. Mi ha salvato dopo aver perso 2 giorni cercando di eseguire il debug di questo problema. Non posso credere che questo non sia menzionato affatto nella guida al passaggio dei messaggi su: developer.chrome.com/extensions/messaging
funforums

6
Apparentemente ho avuto questo problema prima; tornò a rendermi conto che avevo già votato a questo. Questo deve essere in grassetto in grande <blink>e <marquee>tag da qualche parte nella pagina.
Qix - MONICA È STATA MISTREATA il

2
@funforums Cordiali saluti, questo comportamento è ora documentato nella documentazione di messaggistica (la differenza è qui: codereview.chromium.org/1874133002/patch/80001/90002 ).
Rob W,

10
Giuro che questa è l'API più intuitiva che abbia mai usato.
michaelsnowden,

8

La risposta accettata è corretta, volevo solo aggiungere un codice di esempio che semplifichi questo. Il problema è che l'API (a mio avviso) non è ben progettata perché costringe noi sviluppatori a sapere se un determinato messaggio verrà gestito in modo asincrono o meno. Se gestisci molti messaggi diversi questo diventa un compito impossibile perché non sai mai se in fondo a qualche funzione un sendResponse passato verrà chiamato asincrono o no. Considera questo:

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

Come posso sapere se in fondo handleMethod1la chiamata sarà asincrona o no? Come può qualcuno che modifica handleMethod1sapere che interromperà un chiamante introducendo qualcosa di asincrono?

La mia soluzione è questa:

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

});

Ciò gestisce automaticamente il valore restituito, indipendentemente da come si sceglie di gestire il messaggio. Si noti che ciò presuppone che non si dimentichi mai di chiamare la funzione di risposta. Nota anche che il cromo avrebbe potuto automatizzarlo per noi, non vedo perché non lo abbiano fatto.


Un problema è che a volte non vorrai chiamare la funzione di risposta e in quei casi dovresti restituire false . In caso contrario, stai impedendo a Chrome di liberare risorse associate al messaggio.
rsanchez,

sì, ecco perché ho detto di non dimenticare di chiamare il callback. Quel caso speciale che puoi menzionare può essere gestito avendo una convenzione secondo cui il gestore (handleMethod1 ecc.) Restituisce false per indicare il caso "nessuna risposta" (sebbene Id preferisca sempre dare una risposta, anche vuota). In questo modo il problema di manutenibilità è localizzato solo in quei casi speciali di "non ritorno".
Zig Mandel

8
Non reinventare la ruota. I metodi chrome.extension.onRequest/ deprecati chrome.exension.sendRequestsi comportano esattamente come descritto. Questi metodi sono obsoleti perché risulta che molti sviluppatori di estensioni NON hanno chiuso la porta dei messaggi. L'API corrente (che richiede return true) ha un design migliore, perché fallire duro è meglio che perdere silenziosamente.
Rob W,

@RobW ma qual è il problema allora? la mia risposta impedisce allo sviluppatore di dimenticare di tornare vero.
Zig Mandel,

@ZigMandel Se vuoi inviare una risposta, basta usare return true;. Non impedisce la ripulitura della porta se la chiamata è sincronizzata, mentre le chiamate asincrone vengono comunque elaborate correttamente. Il codice in questa risposta introduce complessità inutili per nessun beneficio apparente.
Rob W

2

Puoi usare la mia libreria https://github.com/lawlietmester/webextension per farlo funzionare sia in Chrome che in FF con Firefox senza richiamate.

Il tuo codice sarà simile a:

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 }); });
}) );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.