2

I'm working on a chrome extension that extracts meta data. The code which parses the metadata is contained in a content script. The background.js and content.js communicates via a sendMessage request and response. I'm running into an issue with the asynchronous nature of the sendMessage request and I'm not sure how to fix it (even after reading a litany of the discussions on the issue). Any advice or direction would be appreciated. I suspect I'm not getting how to turn these into callbacks.

background.js:

function onContextClick(info, tab) {    
  if( info["selectionText"] ){  
    var x = getMeta(tab);   
    //do stuff with x       
  }
}

function getMeta (tab) {
chrome.tabs.sendMessage(tab.id, {fetchTag: "meta,name,author,content"}, function(response) {
    //alert(response.data);
    //one thing I tired was to put my "do stuff" embedded here, but that didn't work either         
    return response.data; 
    });
}

var menu_id = chrome.contextMenus.create({"title": "Get Meta", "contexts":["selection"], "onclick": onContextClick});

content.js:

function fetchTag(string) {
    var param = string.split(",");
    return $(param[0] + "["+param[1]+ "=\"" + param[2] + "\"]").attr(param[3]); 
    }

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.fetchTag.length > 0)        
    sendResponse({data: fetchTag(request.fetchTag)});
  });
Steven DAmico
  • 321
  • 4
  • 11
  • Possible duplicate of [How to return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) – wOxxOm Nov 11 '15 at 18:57

5 Answers5

9

From: https://developer.chrome.com/extensions/runtime#event-onMessage

Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one onMessage listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called).

chrome.extension.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.fetchTag.length > 0)        
    sendResponse({data: fetchTag(request.fetchTag)});
    return true;
});

And then it will work with async code.

5

For my case,

  1. simply adding return true as the document suggests doesn't help
  2. setting the listener in onMessage to async and use await to wait for an async response and send it using sendResponse doesn't help

By doesn't help I mean following error occurs in Chrome console:

Unchecked runtime.lastError: The message port closed before a response was received.

After some experiment, I found following works fine for sending a response asynchronously.

// content.js
chrome.runtime.sendMessage({ type: "GET_FOO" }, function (response) {
  console.log(response.foo);
});
// background.js

// replace with a real call that
// needs to run asynchronously
async function getFoo() {
  return "bar"
}

async function sendFoo(sendResponse) {
  const foo = await getFoo()
  sendResponse({ foo })
}

chrome.runtime.onMessage.addListener(
  function (request, sender, sendResponse) {
    if (request.type === "GET_FOO") {
      sendFoo(sendResponse)
      return true
    }
  }
);

Tested on Chrome 92.0.4515.159 (Official Build) (x86_64).

fishstick
  • 2,144
  • 1
  • 19
  • 14
  • This!! I tried for days to translate a Firefox Addon that worked fine with choined promises and all into a Chrome addon. Only this added level of indirection made things work – Hagen von Eitzen May 19 '23 at 11:55
0

I followed everything as the doc (or I thought I did) including returning true, but it didn't work.

After spending hours cursing myself, what I found out that caused the problem was this:

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
  // ...

You noticed it? Yes, the async keyword makes the event handler an async function and it's breaking the functionality.

TL;DR

Make sure your message handler is a synchronous function. Otherwise, return true doesn't help.

technophyle
  • 7,972
  • 6
  • 29
  • 50
0

For those searching for the answer in 2023, if the event listener is async then the resolved value is undefined. To get the value return use the then...catch format while creating event listener.

This wont work

In content Script. The below will return undefined to popup or background script.

(() => {  
  chrome.runtime.onMessage.addListener(
    async (request, sender, sendResponse) => {
      console.log("Message from the popup script:");
      console.log(request);
      setTimeout(() => {
        sendResponse({ message: "Action is done" });
      }, 5000);
      return true;
      // sendResponse({ message: "Action is done" });
    }
  );
})();

The below will return the expected response

(() => {  
  chrome.runtime.onMessage.addListener(
    (request, sender, sendResponse) => {
      console.log("Message from the popup script:");
      console.log(request);
      setTimeout(() => {
        sendResponse({ message: "Action is done" });
      }, 5000);
      return true;
      // sendResponse({ message: "Action is done" });
    }
  );
})();
-1

You can use closures. In this way.

function onContextClick(info, tab) {    
  if( info["selectionText"] ){  
    getMeta(tab, function(x){
      console.log(x);
      //do stuff with x
    });
  }
}

function getMeta (tab, callback) {
chrome.tabs.sendMessage(tab.id, {fetchTag: "meta,name,author,content"}, function(response) {
    //alert(response.data);
    //one thing I tired was to put my "do stuff" embedded here, but that didn't work either
    callback(response.data);
    });
}
Raghvendra Parashar
  • 3,883
  • 1
  • 23
  • 36
  • That helped a bunch - got me started on the right track and it's working for me now. Thanks a ton - just been having trouble wrapping my head around some of it - this one should have been obvious! Appreciate the help. – Steven DAmico Dec 30 '12 at 21:51
  • 1
    `Callbacks` is the correct term, not `closures`. Also this question was asked and answered tons of times so just point to a duplicate one, preferably the one that contains an explanation like [that answer](http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) I always point to. – wOxxOm Nov 11 '15 at 18:56