Расширение Google Chrome content_script sendResponse не работает [дубликат]

Ответ Фабрисио на месте; но я хотел дополнить его ответ чем-то менее техническим, который фокусируется на аналогии, чтобы помочь объяснить концепцию асинхронности .


Аналогия ...

Вчера работа, которую я делал, требовала некоторой информации от коллеги. Я позвонил ему; вот как прошел разговор:

Me: Привет, Боб, мне нужно знать, как мы foo 'd bar ' d на прошлой неделе , Джим хочет сообщить об этом, и вы единственный, кто знает подробности об этом.

blockquote>

Боб: Конечно, но мне понадобится около 30 минут?

blockquote>

Я: Это замечательно Боб. Дайте мне звонок, когда у вас есть информация!

blockquote>

На этом этапе я повесил трубку. Поскольку мне нужна была информация от Боба, чтобы закончить мой отчет, я оставил отчет и пошел за кофе вместо этого, затем я догнал какой-то адрес электронной почты. 40 минут спустя (Боб медленно), Боб позвонил и дал мне информацию, в которой я нуждался. На этом этапе я возобновил свою работу с моим отчетом, так как у меня была вся информация, в которой я нуждался.


Представьте, если бы разговор пошел именно так:

Me: Привет, Боб, мне нужно знать, как мы foo на панели 'd на прошлой неделе.

blockquote>

Боб: Конечно, но мне понадобится около 30 минут?

blockquote>

Я: Это замечательно Боб. Я подожду.

blockquote>

И я сидел и ждал. И ждал. И ждал. В течение 40 минут. Не делать ничего, кроме ожидания. В конце концов, Боб дал мне информацию, мы повесили трубку, и я закончил свой отчет. Но я потерял 40 минут производительности.


Это асинхронное и синхронное поведение

Это именно то, что происходит во всех примерах нашего вопроса. Загрузка изображения, загрузка файла с диска и запрос страницы через AJAX - это медленные операции (в контексте современных вычислений).

Вместо того, чтобы ожидает для выполнения этих медленных операций, JavaScript позволяет зарегистрировать функцию обратного вызова, которая будет выполнена при завершении медленной операции. Тем временем, однако, JavaScript будет продолжать выполнять другой код. Тот факт, что JavaScript выполняет другой код , ожидая завершения медленной операции, делает поведение асинхронным. Если JavaScript выполнил операцию до завершения любого другого кода, это было бы синхронным поведением.

var outerScopeVar;    
var img = document.createElement('img');

// Here we register the callback function.
img.onload = function() {
    // Code within this function will be executed once the image has loaded.
    outerScopeVar = this.width;
};

// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);

В приведенном выше коде мы запрашиваем JavaScript для загрузки lolcat.png, который операция sloooow . Функция обратного вызова будет выполнена после выполнения этой медленной операции, но в то же время JavaScript будет продолжать обрабатывать следующие строки кода; т.е. alert(outerScopeVar).

Вот почему мы видим предупреждение, показывающее undefined; так как alert() обрабатывается немедленно, а не после загрузки изображения.

Чтобы исправить наш код, все, что нам нужно сделать, - переместить код alert(outerScopeVar) в функцию обратного вызова. Вследствие этого нам больше не нужна переменная outerScopeVar, объявленная как глобальная переменная.

var img = document.createElement('img');

img.onload = function() {
    var localScopeVar = this.width;
    alert(localScopeVar);
};

img.src = 'lolcat.png';

Вы всегда будете видеть, что обратный вызов указан как функция , потому что это единственный способ в JavaScript определить код, но не выполнить его до конца.

Поэтому во всех наших примерах function() { /* Do something */ } является обратным вызовом; чтобы исправить все примеры, все, что нам нужно сделать, - это переместить код, который нуждается в ответе операции там!

* Технически вы можете использовать eval(), но eval() для этой цели является злом


Как мне ждать моего вызывающего абонента?

В настоящее время у вас есть код, похожий на этот:

function getWidthOfImage(src) {
    var outerScopeVar;

    var img = document.createElement('img');
    img.onload = function() {
        outerScopeVar = this.width;
    };
    img.src = src;
    return outerScopeVar;
}

var width = getWidthOfImage('lolcat.png');
alert(width);

Однако теперь мы знаем, что return outerScopeVar происходит немедленно; перед тем как функция обратного вызова onload обновила переменную. Это приводит к getWidthOfImage() возврату undefined и undefined.

Чтобы исправить это, нам нужно разрешить функции, вызывающей getWidthOfImage(), зарегистрировать обратный вызов, а затем переместить предупреждение ширины, которая должна находиться внутри этого обратного вызова,

function getWidthOfImage(src, cb) {     
    var img = document.createElement('img');
    img.onload = function() {
        cb(this.width);
    };
    img.src = src;
}

getWidthOfImage('lolcat.png', function (width) {
    alert(width);
});

... как и прежде, обратите внимание, что нам удалось удалить глобальные переменные (в данном случае width).

131
задан Xan 7 September 2014 в 19:16
поделиться

3 ответа

Из документация для chrome.runtime.onMessage.addListener :

Эта функция становится недействительной, когда приемник событий возвращается, если вы не вернете true из прослушивателя событий, чтобы указать, что вы хотите для отправки ответа асинхронно (это приведет к тому, что канал сообщения будет открыт на другом конце, пока не будет вызван вызов sendResponse).

. Вам просто нужно добавить return true; после вызова getUrls, чтобы указать, что вы вызовите функцию ответа асинхронно.

293
ответ дан rsanchez 28 August 2018 в 01:38
поделиться

Принятый ответ правильный, я просто хотел добавить пример кода, который упростит это. Проблема в том, что API (на мой взгляд) не очень хорошо разработан, потому что он заставляет нас разработчиков знать, обрабатывается ли какое-либо конкретное сообщение async или нет. Если вы обрабатываете много разных сообщений, это становится невыполнимой задачей, потому что вы никогда не знаете, если в какой-то степени в какой-то функции переданный метод sendResponse будет вызываться как асинхронный или нет. Рассмотрим это:

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

Как узнать, будет ли в самом низу handleMethod1 вызов асинхронным или нет? Как может кто-то, кто изменяет handleMethod1, знает, что он сломает вызывающего абонента, введя что-то async?

Мое решение таково:

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

});

Это автоматически обрабатывает возвращаемое значение, независимо как вы решаете обработать сообщение. Обратите внимание, что это предполагает, что вы никогда не забудете вызвать функцию ответа. Также обратите внимание, что хром мог автоматизировать это для нас, я не понимаю, почему они этого не сделали.

7
ответ дан Roy Rico 28 August 2018 в 01:38
поделиться

Вы можете использовать мою библиотеку 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 }); });
}) );
1
ответ дан Rustam 28 August 2018 в 01:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: