How to promise in Chrome extensions

The following setup of Message Passing for communicating JSON data from a content script to a popup script is pretty usual:

  chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    if (sender.tab) {
      return;
    }
    switch (request) {
    case 'selected_count':
      const count = selected_count();
      console.log('selected_count:', count);
      sendResponse({
        data: count
      });
      break;
    case 'selected_items':
      const items = selected_items();
      console.log('selected_items:', items);
      sendResponse({
        data: items
      });
      break;
    case 'unselect_all':
      unselect_all();
      break;
    }
  });

However, if some ´items´ contain data that you get asynchronously (like an image), then the setup above will fail if those data are not loaded before the handler returns.

Unfortunately, ´Promise´s are the right tool for the job in this case, but you can’t use them right away, because you can’t send a promise back to the popup and let it handle that, because a message can’t contain a promise, because

A message can contain any valid JSON object (null, boolean, number, string, array, or [my annotation: POJO] object).

Fortunately, according to the documentation,

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

which you can do like I show in the rewrite below, where I use a ´Promise´ object to take care of the asynchronous bits:

  chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    const KEEP_CHANNEL_OPEN = true;
    const CLOSE_CHANNEL = false;
    var result = CLOSE_CHANNEL;
    if (sender.tab) {
      return result;
    }
    switch (request) {
    case 'selected_count':
      const count = selected_count();
      console.log('selected_count:', count);
      sendResponse({
        data: count
      });
      break;
    case 'selected_items':
      selected_items().then(items => {
        console.log('selected_items:', items);
        sendResponse({
          data: items
        });
      });
      result = KEEP_CHANNEL_OPEN;
      break;
    case 'unselect_all':
      unselect_all();
      break;
    }
    return result;
  });

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.