Perpanjangan Pesan Ekstensi Chrome: respons tidak terkirim

151

Saya mencoba menyampaikan pesan antara skrip konten dan ekstensi

Inilah yang saya miliki dalam skrip konten

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

Dan dalam skrip latar belakang yang saya miliki

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

}

Sekarang jika saya mengirim respons sebelum panggilan ajax dalam getUrlsfungsi, respons berhasil dikirim, tetapi dalam metode keberhasilan panggilan ajax ketika saya mengirim respons, ia tidak mengirimnya, ketika saya masuk ke debugging saya bisa melihat bahwa porta null di dalam kode untuk sendResponsefungsi.

Tawaran
sumber
Menyimpan referensi ke parameter sendResponse sangat penting. Tanpa itu, objek respons keluar dari ruang lingkup dan tidak dapat dipanggil. Terima kasih untuk kode yang mengisyaratkan saya untuk memperbaiki masalah saya!
TrickiDicki
mungkin solusi lain adalah dengan membungkus segala sesuatu di dalam fungsi async dengan Promise dan memanggil menunggu metode async?
Enrique

Jawaban:

348

Dari dokumentasi untukchrome.runtime.onMessage.addListener :

Fungsi ini menjadi tidak valid ketika pendengar acara kembali, kecuali jika Anda mengembalikan true dari pendengar acara untuk menunjukkan Anda ingin mengirim tanggapan secara tidak sinkron (ini akan membuat saluran pesan terbuka ke ujung yang lain sampai sendResponse dipanggil).

Jadi, Anda hanya perlu menambahkan return true;setelah panggilan getUrlsuntuk menunjukkan bahwa Anda akan memanggil fungsi respons secara tidak sinkron.

rsanchez
sumber
ini benar, saya menambahkan cara untuk mengotomatisasi ini dalam jawaban saya
Zig Mandel
62
+1 untuk ini. Ini telah menyelamatkan saya setelah menghabiskan 2 hari mencoba men-debug masalah ini. Saya tidak percaya bahwa ini sama sekali tidak disebutkan dalam panduan lewat pesan di: developer.chrome.com/extensions/messaging
funforums
6
Tampaknya saya pernah mengalami masalah ini sebelumnya; kembali untuk menyadari bahwa saya telah meningkatkan suara ini. Ini harus dicetak tebal <blink>dan <marquee>menandai suatu halaman.
Qix - MONICA DISEBUTKAN
2
@funforums FYI, perilaku ini sekarang didokumentasikan dalam dokumentasi pengiriman pesan (perbedaannya ada di sini: codereview.chromium.org/1874133002/patch/80001/90002 ).
Rob W
10
Saya bersumpah ini adalah API paling tidak intuitif yang pernah saya gunakan.
michaelsnowden
8

Jawaban yang diterima benar, saya hanya ingin menambahkan kode sampel yang menyederhanakan ini. Masalahnya adalah bahwa API (dalam pandangan saya) tidak dirancang dengan baik karena memaksa kita pengembang untuk mengetahui apakah pesan tertentu akan ditangani async atau tidak. Jika Anda menangani banyak pesan berbeda, ini menjadi tugas yang mustahil karena Anda tidak pernah tahu apakah jauh di dalam suatu fungsi, sebuah sendResponse yang diteruskan akan disebut async atau tidak. Pertimbangkan ini:

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

Bagaimana saya bisa tahu apakah handleMethod1panggilan itu async atau tidak? Bagaimana seseorang yang memodifikasi handleMethod1tahu bahwa itu akan merusak penelepon dengan memperkenalkan sesuatu async?

Solusi saya adalah ini:

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

});

Ini secara otomatis menangani nilai kembali, terlepas dari bagaimana Anda memilih untuk menangani pesan. Perhatikan bahwa ini mengasumsikan bahwa Anda tidak pernah lupa memanggil fungsi respons. Perhatikan juga bahwa kromium dapat mengotomatisasi ini untuk kita, saya tidak mengerti mengapa mereka tidak melakukannya.

Zig Mandel
sumber
Salah satu masalah adalah bahwa kadang-kadang Anda tidak ingin memanggil fungsi respons, dan dalam kasus itu Anda harus mengembalikan false . Jika tidak, Anda mencegah Chrome membebaskan sumber daya yang terkait dengan pesan.
rsanchez
ya, itu sebabnya saya bilang jangan lupa menelepon callback. Kasus khusus yang Anda sebutkan dapat ditangani dengan memiliki konvensi bahwa pawang (handleMethod1 dll) mengembalikan false untuk menunjukkan kasus "tidak ada respons" (meskipun Id lebih selalu membuat respons, bahkan yang kosong). Dengan cara ini masalah perawatan hanya dilokalkan ke kasus "tidak ada pengembalian" khusus.
Zig Mandel
8
Jangan menciptakan kembali roda. Metode yang usang chrome.extension.onRequest/ chrome.exension.sendRequestberlaku persis seperti yang Anda gambarkan. Metode ini sudah usang karena ternyata banyak pengembang ekstensi TIDAK menutup port pesan. API saat ini (membutuhkan return true) adalah desain yang lebih baik, karena kegagalan hard lebih baik daripada bocor secara diam-diam.
Rob W
@RobW tapi lalu apa masalahnya? jawaban saya mencegah dev dari lupa untuk mengembalikan yang benar.
Zig Mandel
@ZigMandel Jika Anda ingin mengirim respons, gunakan saja return true;. Itu tidak mencegah port dari dibersihkan jika panggilan disinkronkan, sementara panggilan async masih diproses dengan benar. Kode dalam jawaban ini memperkenalkan kompleksitas yang tidak perlu tanpa manfaat yang jelas.
Rob W
2

Anda dapat menggunakan perpustakaan saya https://github.com/lawlietmester/webextension untuk menjadikan ini berfungsi di Chrome dan FF dengan cara Firefox tanpa panggilan balik.

Kode Anda akan terlihat seperti:

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
sumber