Повідомлення з розширенням Chrome проходить: відповідь не надіслана


151

Я намагаюся передавати повідомлення між скриптом вмісту та розширенням

Ось що я маю у content-script

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

}

Тепер, якщо я надсилаю відповідь перед викликом ajax у getUrlsфункції, відповідь надсилається успішно, але в методі успіху виклику ajax, коли я надсилаю відповідь, він не надсилає його, коли я переходжу до налагодження, я можу побачити, що порт нульовий всередині коду для sendResponseфункції.


Зберігання посилання на параметр sendResponse є критично важливим. Без цього об’єкт відповіді виходить із сфери застосування та його не можна викликати. Дякую за код, який натякнув мені на вирішення моєї проблеми!
TrickiDicki

можливо, іншим рішенням є загортання всього в функцію асинхронізації за допомогою Promise та виклик очікування методів асинхронізації?
Енріке

Відповіді:


348

З документації дляchrome.runtime.onMessage.addListener :

Ця функція стає недійсною, коли слухач події повертається, якщо ви не повернете правду від слухача події, щоб вказати, що хочете надіслати відповідь асинхронно (це дозволить каналу повідомлень відкрити до іншого кінця, поки не буде викликано sendResponse).

Тому потрібно просто додати return true;після виклику, getUrlsщоб вказати, що ви будете викликати функцію відповіді асинхронно.


це правильно, я додав спосіб автоматизації цього у своїй відповіді
Зіг Мандель

62
+1 для цього. Це врятувало мене, витрачаючи 2 дні, намагаючись налагодити цю проблему. Я не можу повірити, що це зовсім не згадується в посібнику з передачі повідомлень за адресою: developer.chrome.com/extensions/messaging
funforums

6
Я, мабуть, мав це питання раніше; повернувся до усвідомлення, що я вже підтримав це. Для цього потрібно виділити жирним шрифтом великі шрифти <blink>та <marquee>розмістити теги десь на сторінці.
Qix - МОНІКА ПОМИЛИЛА

2
@funforums FYI, така поведінка тепер задокументована в документації щодо обміну повідомленнями (різниця тут: codereview.chromium.org/1874133002/patch/80001/90002 ).
Rob W

10
Я клянусь, це найбільш неінтуїтивний API, який я коли-небудь використовував.
michaelsnowden

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

});

Це автоматично обробляє повернене значення, незалежно від того, як ви вирішили обробити повідомлення. Зауважте, що це передбачає, що ви ніколи не забудете викликати функцію відповіді. Також зауважте, що хром міг автоматизувати це для нас, я не бачу, чому вони цього не зробили.


Одне питання полягає в тому, що іноді ви не захочете викликати функцію відповіді, і в таких випадках вам слід повернути помилкове . Якщо цього немає, ви забороняєте Chrome звільняти ресурси, пов’язані з повідомленням.
rsanchez

так, саме тому я сказав не забувати зворотній дзвінок. Цей особливий випадок, з яким ви згадуєтеся, може бути вирішений, маючи умову, що обробник (handleMethod1 тощо) повертає помилкове значення, щоб вказати випадок "без відповіді" (хоча Id я завжди завжди робив відповідь, навіть порожній). Таким чином, проблема ремонтоздатності локалізується лише в тих спеціальних випадках "без повернення".
Зіг Мандель

8
Не вигадуйте колесо заново. Застарілі chrome.extension.onRequest/ chrome.exension.sendRequestметоди ведуть себе так, як ви описуєте. Ці методи застаріли, оскільки виявляється, що багато розробників розширень НЕ закрили порт повідомлень. Поточний API (вимагає return true) - це краща конструкція, тому що вийти з ладу - це краще, ніж мовчки протікати.
Роб Ш

@RobW, але в чому тоді проблема? моя відповідь заважає дияволу забути повернути правду.
Zig Mandel

@ZigMandel Якщо ви хочете надіслати відповідь, просто використовуйте return true;. Це не перешкоджає очищенню порту, якщо виклик синхронізований, тоді як виклики асинхронізації все ще правильно обробляються. Код у цій відповіді вводить зайву складність без видимої користі.
Роб Ш

2

Ви можете використовувати мою бібліотеку https://github.com/lawlietmester/webextension, щоб зробити цю роботу в Chrome і FF способом Firefox без зворотних викликів.

Ваш код буде мати такий вигляд:

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 }); });
}) );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.