28

I am having an issue of asynchronicity (I believe). sendResponse() in contentscript.js does not wait for getThumbnails() to return.

I am sending a message in popup.js:

chrome.tabs.sendMessage(tabs[0].id, {message: "get_thumbnails", tabUrl: tabs[0].url},
      function (respThumbnails) {
            const thumbUrl = respThumbnails.payload;
            console.log("payload", thumbUrl)   
      }
);

Then, in contentscript.js I listen for this message:

chrome.runtime.onMessage.addListener(async function(request,sender,sendResponse) {
    if(request.message === "get_thumbnails") {
        const payload = await getThumbnails();
        console.log("thumbPayload after function:", payload)
        sendResponse({payload:payload});   
    }
});


async function getThumbnails() {
    let tUrl = null;
    var potentialLocations = [
        {sel: "meta[property='og:image:secure_url']",   attr: "content" },
        {sel: "meta[property='og:image']",              attr: "content" },
    ];

    for(s of potentialLocations) {
        if(tUrl) return
        const el = document.querySelector(s.sel);
        if(el) {
            tUrl = el.getAttribute(s.attr) || null;
        } 
    }
    return tUrl;
};

But it is also possible that the problem is coming from my getThumnails() function, because most of the times, payload is null and not undefined. So getThumbnails() might return before it is completely executed. If this is the case, I have no idea why...

I also tried this code for getThubnails():

async function getThumbnails() {
  let x = await function() {
    let tUrl = null;
    var potentialLocations = [
        {sel: "meta[property='og:image:secure_url']",   attr: "content" },
        {sel: "meta[property='og:image']",              attr: "content" },
    ];

    for(s of potentialLocations) {
        if(tUrl) return
        const el = document.querySelector(s.sel);
        if(el) {
            tUrl = el.getAttribute(s.attr) || null;
        } 
    }
    return tUrl;
  }
  return x;
};

But this does not work, it seems to break my code...

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
Jerlam
  • 943
  • 2
  • 12
  • 31

1 Answers1

74

The callback of onMessage should return a literal true value (documentation) in order to keep the internal messaging channel open so that sendResponse can work asynchronously.

Problem

Your callback is declared with async keyword, so it returns a Promise, not a literal true value. Chrome extensions API doesn't support Promise in the returned value of onMessage callback until https://crbug.com/1185241 is fixed so it's just ignored, the port is immediately closed, and the caller receives undefined in response.

Solutions

Remove the async keyword from before (request, sender, sendResponse), then...

Solution 1 Call an async function that can be embedded as an IIFE:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.message === "get_thumbnails") {
    (async () => {
      const payload = await getThumbnails();
      console.log("thumbPayload after function:", payload)
      sendResponse({payload});
    })();
    return true; // keep the messaging channel open for sendResponse
  }
});

Solution 2 Declare a separate async function and call it from the onMessage listener:

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.message === "get_thumbnails") {
    processMessage(msg).then(sendResponse);
    return true; // keep the messaging channel open for sendResponse
  }
});

async function processMessage(msg) {
  console.log('Processing message', msg);
  // .................
  return 'foo';
}
Kalnode
  • 9,386
  • 3
  • 34
  • 62
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • Thanks for your answer. However, I have tried with your code and it still does not work. it returns null. Is everthing fine with my getThumbnails() function? Beside the await document.querySelector, which I removed :) – Jerlam Oct 27 '18 at 18:55
  • When you edit content scripts you need to reload the extension on chrome://extensions page and also reload the web page where that content script runs. As for that function, I don't see anything that needs `async` and `await` inside at all. – wOxxOm Oct 27 '18 at 19:10
  • Yes of course, I reload the extension and refresh the page all the time. Your code is actually working, but the reason why I am still having an issue is because the page I am on has like 6 or 7 frames. As I have "all_frames"=true in my manifest, a new message is send for each of the frames, and all of them sendResponse to my popup.js, but popup.js receives only 1 response, even tough 6 were sent... I don't know if I am clear or not, but for instance, I do console.log("location:", document.location) in my contentscript, and it consoles log 6 different locations... I cannot find a solution.. – Jerlam Oct 27 '18 at 19:44
  • The iframe issue is a different question. Off the top of my head, you can simply add a runtime.onMessage listener in your popup and use runtime.sendMessage in your content script instead of sendResponse. Or you can switch to tabs.executeScript instead of declared content scripts with messaging, so you'll collect the output from all frames in one array. – wOxxOm Oct 28 '18 at 05:11
  • I have the same code in a popup window and a browser action menu, and an onMessage handler in a persistent background page. Weirdly, after making the handler async, the popup worked perfectly. It was only after going back to test the menu that I realized something had broken. If the popup wasn't also open, the menu worked fine. But if it was, awaited async calls to the background made from the menu never received responses. Something about having two pages open (and two ports) seemed to break it. Moving the awaits out of the handler fixed things. – jdunning Sep 05 '20 at 23:09
  • @jdunning, it means there was another mistake in your code that was canceled out. There's no point in discussing it without seeing [MCVE](/help/mcve). – wOxxOm Sep 06 '20 at 04:04
  • 3
    This didn't work for me until I removed the `async` keyword within the top-level `addListener` function. `chrome.runtime.onMessage.addListener(async function (` >>> `chrome.runtime.onMessage.addListener(function (` – Vadorequest Jun 15 '22 at 19:35
  • 1
    @Vadorequest, you did exactly what this answer is showing. I've added an explicit note to clarify it. – wOxxOm Jun 16 '22 at 05:05
  • Yes, indeed! I just struggled to reproduce exactly the same thing, as I started from existing code I didn't notice the `async` keyword was there and I thought your solution didn't work, until I spotted/removed it. – Vadorequest Jun 16 '22 at 06:47
  • 1
    You just saved me! For future readers, REMOVE THE ASYNC!!! Use thens, the sendReponse and the return true and it will solve all your problems! – Pedro Silva Dec 09 '22 at 12:04
  • Solution 1 didn't work for me. Solution 2 did the trick! Cleaner too. – Kalnode Jan 04 '23 at 00:13