
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 returntrue
from the listener function. This indicates that you intend to send a response asynchronously. If you do not returntrue
, the message port will close before the response is sent, leading to errors likeThe 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
:
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.
Comments