Promises betweeen content and background script using connection-based messaging, runtime.connect and postMessage

I’m struggling to communicate with promises between scripts.

Once a runtime connection is established, I’m trying this in the background script and the equivalent in the content script, in attempt to establish a promise chain:

let id = p.sender.tab.id;
portFromCS[id].onMessage.addListener( message );  

function message( o, p )
  {
    return new Promise( ( resolve, reject ) =>
      {
         resolve( { 'o' : o, 'p' : p } );
      } ).then( ( b ) => { return f[b.o.func]( b.o.args, b.p ); } )
         .then( ( r ) => { return r; } );

Also tried…

function message( o, p )
  {
    return new Promise( ( resolve, reject ) =>
      {
        f[o.func]( o.args, p ).then( (r) => { resolve( r ); }, ( e ) => { reject( e ); } );
      } ); // close new promise
  } // close message

function message( o, p )
  {
    return f[o.func]( o.args, p ).then( (r) => { return r; }, ( e ) => { return e; } );
  } // close message

I haven’t added the rejection handlers in at this point.

The f[b.o.func]( b.o.args, b.p ) is invoking a function of the object f by string name and passing it the arguments sent in the postMessage and the sender data object. This part was working fine for a several weeks. It is the addition of the promise attempt that is causing the trouble.

There are no errors but when the content script posts the message in the background script to invoke the function of f, which is a promise itself, it doesn’t wait before processing the next then. The content script has the following (the rejection handlers have been removed to make code easier to read here).

    DB.open_db( {} )
      .then( ( ) => { return myPort.postMessage( { 'func' : 'send_data', 'args' : '' } ); } )
      .then( (r) => { console.log(r); return unpack(r); }  )
      .then( (r) => { console.log(r); },  ( msg ) => { console.log( msg ); })
      .then( ... );

I’m asking the content script to open the page’s indexedDB database and then post a message to the background script requesting the invocation of the send_data function, which is a promise.

The send_data function is invoked in the background script and complete successfully there, but the second then statement in the content script runs immediately, as if it returned with successful posting of the message rather than waiting for send_data to resolve or reject. So, r in the content script is always undefined.

For testing, I have been using a setTimeout in the send_data function in the background script of:

 f.send_data = function( o, p ) 
  {
    return new Promise( ( resolve, reject ) =>
      {
         setTimeout( () => {
             resolve( 'Test data retrieved successfully. Here it is!' );
          }, 10000 );          
       } );// close new promise
}; // close send_data

Could you please tell me what it is that I’m doing wrong and not understanding? I was expecting to be able to add a few thens to the promise of send_data and have the background script wait for all of them to complete before sending a response back to the content script.

If I haven’t been clear, the specific problem I’m having is that I can’t get the content script’s promise chain to wait the ten seconds it takes for send_data to complete in the background script before processing the following then statement; or, more likely, I can’t get the background script to wait the ten seconds before it sends back a resolve to the postMessage.

Thank you.

I referenced these two documents to start:

and would like to know if and how they can be used with promises, as described in this document

provided by freaktechnikMartin Giger in response to this similar question How to make Promises work between background and foreground scripts?.

First off, a couple of notes on Promise usage. You do not need to wrap a Promise into new Promise and then call resolve in a then. You can just return the Promise. You can also use Promise.resolve(value) to create a promise resolving to value.
You can also leave out { return ... } in arrow functions, if you don’t wrap it in {}the contained value is also the return value.
Lastly, you can just do .then(console.log, console.error) or .catch(console.error) to log the results of a Promise.

Lastly, your variable naming makes this a bit hard to follow, but based on what I understand your code does this should work fine. One possible reason why this wouldn’t work is if you had multiple message listeners on the same port. Only one message listener can respond to a message. I assume you’re just opening a port per content script and adding an onMessage listener on both sides?

Thank you. You are correct that there is one port per content script and one onMessage listener on both sides. One script is injected at the time the page loads, via the manifest, and other scripts are added to the page with tabs.executeScript. I am sending the postMessage from one of these other scripts. The other scripts do not establish a separate connection, they are just using/referencing the connection set up in the very first content script.

The variable naming is hard to follow. The postMessage from the content script passes an object with two top-level properties, ‘func’ a string for the function name in the background script and ‘args’ for the arguments to pass it.

I thought I ought to be able to return the f[o.func] (o.args).then(…) when f[o.func] is a promise. That is the last message function listed above. The f[o.func] here is the f.send_data function. But, I can’t get it to wait for send_data to complete. In the background script, it waits for send_data to complete but the message handler returns right away.

I still haven’t been able to figure it out.

I guess I waill just start over with nothing in the scripts but the communication ports and see if that will work, and, if so, then add blocks of code back in and determine when it stops working.

Thank you.

The issue appears to be centered about the following.

Removing the extended promise chain starting with the DB.open_db() in the content script and trying to execute just this statement alone, completes without error.

myPort.postMessage( { 'func' : 'send_data', 'args' : '' } );

But if attempt this statement, the return from browser.tabs.executeScript() is an error stating that ‘myPort.postMessage(…) is undefined’.

myPort.postMessage( { 'func' : 'send_data', 'args' : '' } ).then( console.log );

No matter what form of onMessage listener is placed in the background script, myPort.postMessage appears to not be considered a promise.

The last method I tried was: ( o is the message and p is the sender object )

portFromCS[id].onMessage.addListener( ( o, p ) => { return Promise.resolve().then( () => { return f[o.func]( o.args, p ); } ).then( () => { log('The final then.'); } ); } );

The ‘Final then.’ is written to the log function ten seconds later. But if attach a then to the myPort.postMessage, the content script errors.

Neither does this work as shown in another question for sendMessage.

(async function()
  {
    let r = await myPort.postMessage( { 'func' : 'send_data', 'args' : '' } );
    console.log(r);
})();

I also tried it the other way round, meaning having the promise in the onMessage of the content script and having the background script wait for it to complete, and get the same error when try to attach a then to the postMessage.

I must be missing something fundamental because I think I’m doing just like the examples conerning sendMessage but on postMessage and I can’t get it to be recognized as a promise.

Fortunately, using the messages to invoke functions in the different scripts, I can accomplish what is needed without promises working across scripts. Perhaps, it is safer to keep promises within scripts only and invoke different functions across scripts depending on the results of the promises. That is about all the promises are doing anyway, at least in my attempted use of them here, except that it looks like neater code with the chaining of then methods.

Thank you.

I’m going to attempt a very partial reply, since I’m interested in this topic.

In my understanding, a Promise cannot be sent from one process to another, or from one HTTP connection to another, because it contains optional executable code (which starts the asynchronous operation) and at least two function values (the resolve function and the reject function). But text messages can certainly be sent (such as from content to bg script), and these could be used to regenerate the Promise at the receiving end.

Note that direct text messages can be sent from content to bg without using persistent connections, and I think there might be an option in this case to create a Promise on the receiving end to report when the communications finishes (or maybe even when a reply message finishes?).

I’d appreciate any corrections, as I’m having trouble understanding all of this.

The goal here isn’t to send a promise. Instead, Firefox supports replying to messages from the onMessage handler by promise instead of callback. Returning a promise from that handler will send the value it resolves to back to the message sender, and the postMessage call there should then resolve with that value.

Got it! Thanks. I know about the Promise for a return message and haven’t got that working yet, and I’ll get back to it when I have time.

I apologize for being unclear. I’m not trying to send a promise from one script to another but want to use postMessage in the sending script as a promise so that a then can be attached to it that will wait for a promise on the other side to complete before executing the appropriate code in the then.

My problem is that I cannot get the postMessage to be “treated” as a promise in the sending script. The script errors if a then is attached to it; and if attempt to return it inside a then, it completes immediately without waiting for the promise on the receiving side to complete and any reference to the result is always undefined.

My question now is how does the sending script know that the onMessage handler of the receiving script is going to return a promise so that postMessage in the sending script is treated as one, that is, will wait for a response?

The message is sent to the receiving script and the onMessage handler processes as a promise but the sender script does not treat postMessage as a promise and does not wait for the receiving script’s response.

Is there a difference between sendMessage and postMessage such that sendMessage can be a promise and postMessage cannot? It appears that sendMessage was sort of waiting for a response in the past through sendResponse option of the onMessage listener. I can find thorough documentation on runtime.sendMessage and it states it has a return value. I cannot find a similar page for runtime.postMessage on MDN but only a brief explanation in the runtime.Port documentation, below, and an asynchronous example is not provided.

A return value is not mentioned; so, perhaps, postMessage cannot be treated as a promise because it is not set up to receive a return value, be it synchronous or asynchronous. Perhaps, I am full of bologna, because I am just speculating here.

I don’t really understand the difference between sendMessage and postMessage, and simply used the example provided in the content script documentation mentioned in the original question for setting up a longer-lived connection, and I wanted to have an array of ports in the background script communicating with multiple content scripts. I didn’t see a way of doing so with sendMessage.

Thank you.

Oh, you have a good point. I didn’t even really think about it, but yeah, ports don’t have the whole reply system. You can easily build one yourself though, since you control both sides of the port ultimately.

My solution to that is https://github.com/freaktechnik/justintv-stream-notifications/blob/master/src/port.js

Thank you for the link. I will look it over and see if I can figure out how to make use of it. I mean because I am fairly new to all of this and might not understand it all to start.

Since I started this question, I thought I’d share a solution that works. I’m not implying it is an especially good one but it works and would have been helpful to me in the beginning. I welcome advice from those who know how to do it better. The purpose was to find a way to have a postMessage that waits for a promise on the receiving side to complete.

This example opens a database in a content script, then requests that the background script send it data, but the background script has to gather some information through a promise and pass it to the content script of a different tab that has the database, which extracts the data and sends a result through the resolution of a promise back to the background script, which completes its promise and sends a result back to the original sending content script. None of the database code is included here. The data extraction function is just a promise with a setTimeout to see if the scripts wait for it to complete.

All of the message listeners just invoke a method of an an object f. The sender of the message passes an object that has the method name as a string (‘func’) and an object of arguments(‘args’). In the case of a promise scenario, the ‘args’ object also includes the name of the method/function that posted the message as property ‘caller’. In the background script, the sender object is passed to the function as p.

So, background script:

 portFromCS[id].onMessage.addListener( ( o, p ) => { f[o.func]( o.args, p ); }); 

And content scripts:

myPort = browser.runtime.connect();
myPort.onMessage.addListener( ( o ) => { f[o.func]( o.args ); });

Since postMessage doesn’t wait for a synchronous or asynchronous return, an object ‘prms’ is declared with a method that returns a promise. The promise posts the message that invokes the function in the other script and defines two methods: resolve and reject. But the methods are invoked only when the other script posts a message back to do so. That is generalized in the f.resolve_promise function. The other script doesn’t post that message until its promise completes.

None of this is really needed, because each side could just post a message back at the completion of each stage; but those operations couldn’t be chained neatly together in a series of then methods. Use of the ‘prms’ object makes that possible.

I’m sure a better programmer can make this cleaner and more efficient, or employ a better method altogether. I put it here only as a start that at least works, especially for those who are novices like me and learning. Thank you.

In the sending content script:

let prms = new Object();
prms.req_data = function()
  {
    return new Promise( ( resolve, reject ) =>
      {
        myPort.postMessage( { 'func' : 'send_data', 'args' : { 'caller' : 'req_data' } } );
        prms.req_data.resolve = function( r ) { resolve( r ); r = null; };
        prms.req_data.reject = function( e ) { reject( e ); e = null; };
      } ); // close new promise     
  }; // close req_data

f.resolve_promise = ( o ) => { prms[o.caller][o.status](o.result); }; // close resolve_promise

DB.open_db( {} )
     .then( () => {  return prms.req_data(); },  console.error )
     .then( ( r ) => {  console.log( 'Received Data => ' + r  );  }, console.erorr );

In the background script:

let prms = new Object();
prms.send_data = function( o )
  {
    return new Promise( ( resolve, reject ) =>
     {
        portFromCS[ o.id ].postMessage( { 'func' : 'get_data', 'args' : { 'caller' : 'send_data', 'key' : o.key } } );
        prms.send_data.resolve = function( r ) { resolve( r ); r = null; };
        prms.send_data.reject = function( e ) { reject( e ); e = null; };
      } ); // close new promise     
}; // close send_data

f.resolve_promise = ( o ) => { prms[o.caller][o.status](o.result); }; // close resolve_promise

f.send_data = function( o, p ) 
 { 
   let r;
   r = browser.tabs.query( { 'url' : u } )
        .then( (tab) => { return prms.send_data( { 'id' : tab[0].id, 'key' : f.dup.data } ); }, (e) => {  } )
        .then( (r) => reply( r, true ), (e) => reply( e, false) ); 

 function reply( data, b )
   {
     let r = { 
       'caller' : o.caller, 
       'status' : b ? 'resolve' : 'reject', 
       'result' : data
       };
     portFromCS[ p.sender.tab.id ].postMessage( { 'func' : 'resolve_promise', 'args' : r } );
     data = b = r = null;

   } // close 
}; // close send_data

In the content script that has the data to return:

f.get_data = function( o ) 
  {
    get_result().then( (r) => reply(r), (e) => reply(e) );

   function reply(data)
     {
       myPort.postMessage( { 'func' : 'resolve_promise', 'args' : data } );
       data = null;
    } // close reply

 function get_result() 
   {
      return new Promise( ( resolve, reject ) =>
        {
           let r = { 
           'caller' : o.caller, 
           'status' : 'resolve', 
           'result' : 'Extracted data! => ' + o.key
           };
          setTimeout( () => {  resolve(r); r = null; }, 10000 ); 
       } );// close new promise
   } // close get_result
}; // close get_data

Thanks again for the link to the code. Much of it is beyond my current skill level but I think I get the general idea.

However, I don’t understand what npm is even after reading about it, as far as how and why one would use it. For example, on the main page of the add-on the code you linked to is from it states that one needs to install npm to build that add-on. Does that mean that one using the add-on has to have npm installed? I get that there are packages of code that people share that can be assembled to build projects more quickly, if one knows what the code is doing. But at the most basic level I don’t understand what npm and node.js have to do with building an extension that others can use if they don’t have those installed, or what they provide beyond the web extension APIs.

I’ve read about node.js, Express, and Electron and haven’t been able to grasp how they fit together and what they are used for, at least in terms of an extension.

I realize that this is not related to the original question and do not expect you to provide an answer. In experimenting with web extensions to get access to APIs not available in a standard web-page script, and reading the examples linked to in MDN documents, things seem to blow up quickly and it appears others build their extensions inside some other software. And I just write code in notepad and place my files in a manifest. So, I’m a bit behind in understanding much of where the code you linked to ultimately runs.

Thank you.

My extension is built using packages that are on npm. You don’t need npm nor node to run it once it’s built. It’s a one-time build step that assembles all the files into their final form, if you will. That’s to make the ES6 modules not modules and to convert React JSX to actual JS, pull in new translations etc. web-ext, a tool to help with extension development, is also on NPM.

What NPM does is mostly unrelated to the file I linked, except that it makes it so that the file can be an ES6 module and still be used in places where that’s hard to use, like a content script (that I don’t have in that extension).