How to fix “Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received”

To solve errors related to the asynchronous response

In the previous post, I learned how to implement send/receive messages asynchronously in the browser extension.

In this post I introduce a sample code of sending a message and returning values asynchronously by showing how to fix the errors related to the asynchronous response.

This background.js has so many problems, and it gets error “Error: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received”.

let doubleClickedTab = null;
let base64Favicon = null;

async function ConvertPngToBase64(imageURL) {
    try {
        const response = await fetch(imageURL);
        const blob = await response.blob();
        return await new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result);
            reader.onerror = reject;
            reader.readAsDataURL(blob);
        });
    } catch (error) {
        console.error("Error:", error);
        return null;
    }
}

chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
    if (message.action === 'tabDoubleClicked') {
        doubleClickedTab = message.tabInfo;

        // Convert PNG Favicon to Base64 format
        const faviconUrl = `https://www.google.com/s2/favicons?domain=${doubleClickedTab.url}`;
        base64Favicon = await ConvertPngToBase64(faviconUrl);

        // Send Base64-Favicon to content.js
        chrome.tabs.sendMessage(sender.tab.id, {
            action: "sendBase64Favicon",
            favicon: base64Favicon
        });

        sendResponse({ success: true });
        return true;
    }
});

What is wrong with the background.js?

  • chrome.runtime.onMessage.addListener()
    • Listener side, Async functions are not supported because they return a Promise, which is not supported. (See Chrome Extension API – Message passing)
      async (message, sender, sendResponse) => { } is not right way. Let’s remove async.
    • To send message with using chrome.tabs.sendMessage() doesn’t make sense. Let’s use SendResponse function instead of sending new message to return values.
  • ConvertPngToBase64() method
    • It doesn’t need to be async function. Let’s call this function normally and send “return true” immediately to let the content script know that there is an async response later. Doing so, the message channel will be kept opening.
    • Returning promise object does not make sense. Let’s use SendResponse function to return values.

Fixed code

Here is the fixed code.

I fixed to use sendResponse() function always to return values to the content script. Also I fixed to use fetch/then to achieve the asynchronous response.

The code turned simple and short and easy to read!

function ConvertPngToBase64(imageURL, sendResponse) {
    try {
        fetch(imageURL)
        .then(response => response.blob())
        .then(blob => {
            const reader = new FileReader();
            reader.onloadend = () => {sendResponse({ favicon: reader.result });};
            reader.onerror = () => {sendResponse({ favicon: null });};
            reader.readAsDataURL(blob);
        })
        .catch(error => {sendResponse({ favicon: null });});
    } catch (error) {
        sendResponse({favicon: null});
    }
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'tabDoubleClicked') {
        var doubleClickedTab = message.tabInfo;

        // Convert PNG Favicon to Base64 format
        const faviconUrl = `https://www.google.com/s2/favicons?domain=${doubleClickedTab.url}`;
        ConvertPngToBase64(faviconUrl, sendResponse);
        return true;
    }
});

This is the fixed and working well content.js.

I decided to use promise type to get a response. With this way, const response has the response from the AddListener() in the background.js.

document.addEventListener('dblclick', async () => {
    const response = await chrome.runtime.sendMessage({
        action: "tabDoubleClicked",
        tabInfo: { title: document.title, url: location.href }
    });
    
    var draw = SVG().size(100, 100);
    draw.image(response.favicon).size(100, 100);
    setFavicon(draw);
});