javascriptpromisesynchronous

JavaScript Promises and synchronous behaviour


I am trying to deserialise a JSON object which has a type key whose value is a string; if the type is "a", the object class is defined in "a.js" and this file contains an initialisation function a_init. There may be a subobjects key whose value is an array of objects. If so, these nested objects should be processed in the same way.

The code I have looks like this:

var files = [];

function load(obj) {
  if (!!obj["type"]) {
    let type       = obj.type;
    let subobjects = obj.subobjects;
    let promise    = new Promise(function(resolve) {
                         let script = document.createElement("script");
                         script.setAttribute("src", type + ".js");
                         script.addEventListener("load",resolve);
                         document.head.appendChild(script);
                     });
    promise.then(function() {
        if (!files.includes(type)) {
          files.push(type);
          if (subobjects instanceof Array) {
            for (element of subobjects) {
              load(element);
            }
          }
          let func = window[type + "_init"];
          if (typeof(func) =="function") {
            func(obj);
          }
        }
    });
  }
}

For an object like this:

{ "type": "a",
  "data": "foo",
  "subobjects": [
      { "type": "b",
        "data": "1"
      }
  ]
}

the output is:

This is A
This is B

(The functions a_init and b_init just display a message for testing purposes.)

My problem is that I would like the subobjects to be initialized before the outer object, so b_init would complete before a_init to give this output:

This is B
This is A

but of course the nested call to load just creates a new promise and then completes before the new promise does.

Is there any way to enforce synchronicity here to control the order of events?


Solution

  • You should convert your load function to async and inside it await for the promise and also for the recursive calls to finish.

    var files = [];
    
    // using an async function here so that we can 
    // use await inside it, but also so that we can
    // await the whole function (it is recursively called
    // and we want the recursions to fully complete before 
    // continuing)
    async function load(obj) {
      if (!!obj["type"]) {
        let type = obj.type;
        let subobjects = obj.subobjects;
        const promise = new Promise(function(resolve) {
          let script = document.createElement("script");
          script.setAttribute("src", type + ".js");
          script.addEventListener("load", resolve);
          document.head.appendChild(script);
        });
        
        await promise; // wait for the script to be loaded 
        
        if (!files.includes(type)) {
          files.push(type);
          if (subobjects instanceof Array) {
            for (element of subobjects) {
              // await for the nested object to completely run 
              // before continuing. This will go deep first
    
              await load(element);
            }
          }
          let func = window[type + "_init"];
          // we reach this place only after all nested objects
          // completed.
          if (typeof(func) == "function") {
            func(obj);
          }
        }
      }
    }