Search code examples
javascriptgoogle-chrome-extension

sendResponse not waiting for async function or Promise's resolve


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...


Solution

  • Chrome still doesn't support Promise in the returned value of onMessage listener both in ManifestV3 and V2. Click the star in https://crbug.com/1185241 to be notified of progress.

    Since your listener is declared with async keyword, it returns a Promise, not a literal true value as required by onMessage implementation, so the returned Promise with your value is just ignored, the port is immediately closed, and the caller receives undefined in response.

    Making your listener compatible

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

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

    Patching the API to allow an async/Promise listener

    Add the following at the beginning of each script that uses chrome.runtime.onMessage

    if ('crbug.com/1185241') { // replace with a check for Chrome version that fixes the bug
      const {onMessage} = chrome.runtime, {addListener} = onMessage; 
      onMessage.addListener = fn => addListener.call(onMessage, (msg, sender, respond) => {
        const res = fn(msg, sender, respond);
        if (res instanceof Promise) return !!res.then(respond, console.error);
        if (res !== undefined) respond(res);
      });
    }
    

    ...and then you can simply return the value:

    chrome.runtime.onMessage.addListener(async msg => {
      if (msg === 'foo') {
        const res = await fetch('https://foo/bar');
        const payload = await res.text();
        return {payload};
      }
    });