64

I want to use async await in an onMessage listener:

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) =>{
    var key = await getKey();
    sendResponse(key);
});

However I get undefined when I send a message.

From the documentation for chrome.runtime.onMessage.addListener:

If you want to asynchronously use sendResponse(), add return true; to the onMessage event handler.

This works when I use a callback.

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) =>{
    getKey(key => {
        sendResponse(key);
    });
    return true;
});

However I would like to leverage the await syntax. But it does not seem to work and still returns undefined:

chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) =>{
    var key = await getKey();
    sendResponse(key);
    return true;
});
mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Chris
  • 13,100
  • 23
  • 79
  • 162

8 Answers8

98

tl;dr

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  (async () => {
    // async code goes here
    // ...
    const result = await getSomething();
    sendResponse(result);
  })();

  // Important! Return true to indicate you want to send a response asynchronously
  return true;
});

Or extract to an async function.

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  doSomethingWith(request).then(sendResponse);
  return true; // return true to indicate you want to send a response asynchronously
});

async function doSomethingWith(request) {
  var key = await getKey();
  // await .....
  return key;
}

The return value of an async function is implicitly wrapped in Promise.resolve. See async doc.

The return true; statement did the trick. It tells Chrome that you want to send a response asynchronously.

See onMessage.

Or extract to a utility

If you feel that you will use it most frequently, create a utility, something like:

const wrapAsyncFunction = (listener) => (request, sender, sendResponse) => {
  // the listener(...) might return a non-promise result (not an async function), so we wrap it with Promise.resolve()
  Promise.resolve(listener(request, sender)).then(sendResponse);
  return true; // return true to indicate you want to send a response asynchronously
};

chrome.runtime.onMessage.addListener(
  wrapAsyncFunction(async (request, sender) => {
    console.log(request, sender);

    const key = await getKey();
    // await .....
    return key;
  })
);

Or more generic

Use mozilla/webextension-polyfill if you prefer to "cross browsers" extension.

Example:

var browser = require("webextension-polyfill");

browser.runtime.onMessage.addListener(async (msg, sender) => {
  console.log("BG page received message", msg, "from", sender);
  console.log("Stored data", await browser.storage.local.get());
});

browser.browserAction.onClicked.addListener(() => {
  browser.tabs.executeScript({file: "content.js"});
});
ninhjs.dev
  • 7,203
  • 1
  • 49
  • 35
10

The closest I could get:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    (async () => {
        var key = await getKey();
        sendResponse(key);
    })();
    return true;
});
daghan
  • 948
  • 10
  • 18
4

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

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

have to say the official doc is really hard to read.

zyfyy
  • 323
  • 3
  • 10
  • Sadly it's even worse now - that text has been removed. That link now just says "returns boolean | undefined" with no further explanation. – mikemaccana May 11 '23 at 12:50
3

Not sure if Chrome extension runtime environment supports the async/await syntax, but you can use a transpiler (i.e. Babel) to have it converted to for example ES5. Then you can define a wrapper function like this:

function asChromeListener(listener) {
  return (message, sender, sendResponse) => {
    const returnValue = listener(message, sender);

    if (isPromise(returnValue)) {
      returnValue.then(sendResponse);
      return true;
    }
    else {
      if (typeof returnValue !== 'undefined') {
        sendResponse(returnValue);
      }
      return false;
    }
  };
}

function isPromise(value) {
  return typeof value === 'object' && value !== null && 'then' in value && 'catch' in value;
}

Which you can then use like:

chrome.runtime.onMessage.addListener(asChromeListener(async (message, sender) => {
  return await doMyAsyncWork(message);
});

Since we use TypeScript, here's also the snippet that we actually use (with generic types).

export function asChromeListener<M, S, R extends Function>(listener: (message: M, sender: S) => any) {
  return (message: M, sender: S, sendResponse: R) => {
    const returnValue = listener(message, sender);

    if (isPromise(returnValue)) {
      returnValue.then(sendResponse);
      return true;
    }
    else {
      if (typeof returnValue !== 'undefined') {
        sendResponse(returnValue);
      }
      return false;
    }
  };
}

function isPromise(value: any) {
  return typeof value === 'object' && value !== null && 'then' in value && 'catch' in value;
}
davido
  • 121
  • 1
  • 7
2

I was unable to use async/await, however I did manage to make it work using then. You need to return true at the very end of your onMessage function.

Here is an example of how I'm asynchronously retrieving the current active tab to my content script.

background.js

chrome.runtime.onMessage.addListener(function (data, sender, sendResponse) {

  if (data.type === 'getTab') {
    const promise = new Promise(async (resolve) => {
      /* gets active tab info */
      let queryOptions = { active: true, currentWindow: true };
      let [tab] = await chrome.tabs.query(queryOptions);

      resolve(tab);
    });

    promise.then((tab) => {
      sendResponse(tab);
    });
  }


  return true;
});

content.js

chrome.runtime.sendMessage(
  {
    type: 'getTab'
  },
  function (data) {

    if (data.type === 'getTab') {
      console.log('my tab', data);
    }
  }
);

I hope this helps someone out there. I've spent more time than I'd care to admit on this one.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Diego Fortes
  • 8,830
  • 3
  • 32
  • 42
  • I need some assistance on my chrome extension project to improve the code to avoid uncaught errors. Are you open for paid help? – Mehul Joisar Jun 19 '22 at 08:43
  • 1
    I like this answer the best - I did use the IIFE async/away approach in the other answer I started to get some cases where the `onMessage()` simply wasn't being run. Also note **proper Promise support is onMessage() is in the standard, and implemented on Firefox, just not in Chrome yet** - see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/Runtime/onMessage. I'll stick with this approach until Chrome gets real Promise support in onMessage() – mikemaccana May 11 '23 at 13:06
0

Honestly, it looks like Google Chrome Extensions don't support the await keyword. I've successfully used asynchronous chrome.runtime.onMessage.addListener's before, and every time I try to use await I see this syntax error in the Chrome Debugging Tools, on the line that I use await:

await not supported

Here's how I've been testing:

I created a very basic listener:

chrome.runtime.onMessage.addListener(function(data, MessageSender, sendResponse) {
    sendResponse(awaitTester);
    var async = true;

    // returns true if asyncronous is needed
    if (async) return true;
});

my awaitTester function looks like this:

function awaitTester() {
    var prom = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('test');
        }, 4000);
    });

    var awaited = await prom;
    return awaited;
}

finally, my message sender is what you'd expect:

chrome.runtime.sendMessage({}, function(message) {
    debugger;
    console.log(message);
});

And in the debugger / console I always get undefined.

Blundering Philosopher
  • 6,245
  • 2
  • 43
  • 59
  • 1
    You don't have a native support for async/await - you need to use some sort of tranapiller for that functionality. – Pavel Durov Jan 14 '19 at 19:40
0

you could try this

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  new Promise(async send => {
     

    var key = await getKey();
    send(key);


  }).then(sendResponse)
  return true;
});
Matt
  • 2,096
  • 14
  • 20
0

Another way that worked for me.

popup.js

chrome.runtime.onMessage.addListener( async (request, sender, sendResponse) => {
    let response = '';
    switch (request.msg){
        case 'login' :
            response = await login();
            sendResponse({ msg: 'login_response', response: response });
            break;
        default:
            sendResponse(response);
    }
});

background.js

       // Send a message to the background script
        chrome.runtime.sendMessage({msg: 'login'});

        // Wait for a response from the background script
        const response = await new Promise(resolve => {
          chrome.runtime.onMessage.addListener(function listener(message) {
            if (message.msg === 'login_response') {
              resolve(message.response);
            }
          });
        });

        console.log('Response received', response);
SkyRar
  • 1,107
  • 1
  • 20
  • 37