On this page
Binary code in the background and hands on the keyboard. Image by Gerd Altmann from Pixabay.

Using async and await with browser.runtime.onMessage in cross-browser extensions

Learn to use async/await with browser.runtime.onMessage for effective cross-browser extension communication.

To effectively use async and await with browser.runtime.onMessage in cross-browser extensions, it’s essential to understand how asynchronous message handling works within the context of browser extensions.

Understanding message passing

The browser.runtime.onMessage API allows different parts of an extension (like background scripts, content scripts, and popup scripts) to communicate with each other. When using async functions as message event listeners, you need to ensure that the listener can handle asynchronous responses correctly.

Key points for using async/await

  • Return true for asynchronous responses: when you define an onMessage listener that uses async/await, you must return true from the listener function. This indicates that you intend to send a response asynchronously. If you do not return true, the message port will close before the response is sent, leading to errors like The message port closed before a response was received.
  • Using Promises: since async functions inherently return a promise, you can wrap your logic in a promise to handle the response. This can be done using a helper function that resolves the promise and sends the response when the asynchronous operation completes.
  • Cross-browser compatibility: while the browser namespace is used in Firefox, Chrome uses the chrome namespace. However, many modern extensions use the browser namespace with a polyfill to ensure compatibility across both browsers. This allows you to write code that works seamlessly in both environments.

Example implementation

Here’s a simple example of how to implement an onMessage listener using async/await:

background.js or service worker
const performAsyncOperation = async (request, sendResponse) => {
  let response;
  
  try {
    response = await globalThis.fetch(request.url, {
      redirect: 'follow'
    })

    sendResponse({
      headers: Object.fromEntries(response.headers.entries()),
      redirected: response.redirected,
      status: response.status,
      statusText: response.statusText,
      type: response.type
    });
  } catch (error) {
    // Handle HTTP error here
    sendResponse({
      error: error
    });
  }
};

const onMessageHandler = (request, _sender, sendResponse) => {
  performAsyncOperation(request, sendResponse);

  return true; // Keep the message channel open for sendResponse
};

chrome.runtime.onMessage.addListener(onMessageHandler);

This approach allows you to handle errors gracefully and ensures that the response is sent back correctly.

The globalThis keyword is used to access the global object in a way that is consistent across different environments (like browsers and Node.js). This ensures that the fetch function is called correctly regardless of the context.

Additionally it is important to use globalThis.fetch explicitly because it accesses the fetch method on the global object, avoiding the need for the prototype lookup chain. globalThis is a property that returns the global object, which is the topmost object in the prototype chain.

Conclusion

Using async and await with browser.runtime.onMessage is straightforward if you remember to return true value from your listener and handle promises correctly. By following these guidelines, you can create robust cross-browser extensions that utilize modern JavaScript features effectively.

Related posts

Comments

Leave a Reply

Search in sitelint.com

Looking for automated testing for technical SEO?

With SiteLint, you can help search engine spiders crawl and index your site more effectively.

Real-user monitoring for Accessibility, Performance, Security, SEO & Errors (SiteLint)