javascriptcallbacksynchronize

How can i synchronize callbacks (from within the callback) called from different sources?


I have the problem with my ui-framework (openui5) synchronizing two different callbacks. One is a callback from a ui-event that a rendering is complete, the other is an asynchronous callback from a data-access (ajax-call), done by a used model class. The ui-event callback needs the data from the data-callback.

To make it simpler here a simplified code snipped which demonstrates the problem. I want, that always onRouteMatched-Code should execute code when onUpdateFinished has finished. In this example i have created the async scenario bei using my asyncInit-Method. This is only for creating an easy test-scenario. in reality this onUpdateFinished is called by my data-model class and onRouteMatched by another framework-class event.

let testClass = {

 myUser : "",

 // Method definition:
 asyncInit : function() {
   me = this;
   let timer1 = Math.round(Math.random()*10000);
   let timer2 = Math.round(Math.random()*10000);

   setTimeout(function(){ me.onUpdateFinished();} , timer1);
   setTimeout(function(){ me.onRouteMatched();} , timer2);
 },

 onUpdateFinished : function(oEvent) {
   console.warn("onUpdateFinished.....setting Data")
   this.myUser = 'doe';
 },

 onRouteMatched : function(oEvent) {
   // Event though this callback is 
   // called before onUpddate
   console.warn("onRouteMatched...., myData="+this.myUser);
 }

}
testClass.asyncInit();

In this example onUpdateFinished and onRouteMatched are called by setTimeout with timeout-value of 0-9 seconds. If onUpdateFinished is called first, everything is ok and user in onRouteMatched is "doe". If onRouteMatched is called before onUpdateFinished the user is "undefined".

A not very clean workaround is to use a "sync"-variable and to check it in onRouteMatched (see example). I also thougth about promisses, async and await and such stuff. But as i see, in all the examples i must have the control over initiating the callbacks in my code. but in this case i dont have influence about the callers of the callback.

Here example with my "workaround"

let testClass = {

 myUser : "",
 updOk  : false,

 // Method definition:
 asyncInit : function() {
   me = this;
   let timer1 = Math.round(Math.random()*10000);
   let timer2 = Math.round(Math.random()*10000);

   setTimeout(function(){ me.onUpdateFinished();} , timer1);
   setTimeout(function(){ me.onRouteMatched();} , timer2);
 },

 onUpdateFinished : function(oEvent) {
   console.warn("onUpdateFinished.....setting Data")
   this.myUser = 'doe';
   this.updOk = true;
 },

 onRouteMatched : function(oEvent) {
   // Event though this callback is 
   // called before onUpddate
   me = this;
   if (!this.updOk) {
     console.log("onRouteMatched called...");
     setTimeout(function(){ 
       console.warn("Update still not finished");
       me.onRouteMatched();
     },1000)
     return;
   }
   console.warn("onRouteMatched...., myData="+this.myUser);
 }

}
testClass.asyncInit();

Solution

  • Build up a promise around the user:

    let userArrived, user = new Promise(resolve => userArrived = resolve);
    

    Then, inside onUpdateFinished, resolve the promise:

    onUpdateFinished : function(oEvent) {
      userArrived("John Doe");
    }
    

    Inside onRouteMatched consume the promise:

    onRouteMatched : function(oEvent) {
      user.then(username => {
        console.log(`${username} is ready!`);
      });
    }
    

    Or using async / await that could even be written as:

    async onRouteMatched(oEvent) {
      const username = await user;
      console.log(`${username} is ready!`);
    },