Search code examples
javascriptasynchronousasync-awaites6-promisedynamic-import

What is the best way to make this code synchronous?


I have the below code where I make an API call and store the response to window. This is done in order to access the value anywhere in the application. But since rest of the application is dependent on this code i want to make this synchronous, that is, rest of the JS should execute only after the api call is done and data is stored to global object. What is the best way to solve this?

Note: This is a sample of the use case. I can't use methods like .then as this is first import in an HTML file having several JS file each of them being dependent on the response of this.

    class GlobalConstants {
      constructor() {
        this.dynamicData = {}; 
        this.getConfigData(); 
      }
 
      async getConfigData() {
        try {
          const configResponse = await fetch("https://jsonplaceholder.typicode.com/todos/1");
          if (configResponse.status === 200) {
          const dummyData = await configResponse.json();
          this.dynamicUrl = dummyData;
          } else {
            throw new Error(`Config API threw ${formattedConfigResponse.status}`);
          }
        } catch (error) {
          console.warn("Failed to fetch:", error);
        }
      }
    }
    
window.globalConstants = new GlobalConstants();


console.log(globalConstants.dynamicData)

In the example, instead of consoling empty object it should ideally print the api response.

My html looks like:

<!DOCTYPE HTML>
<html>
<head>
    <script src="https://test.com/script1.js"></script> // This contains the code which I shared
    <script src="https://test.com/script2.js"></script> // This consumes the data. Hence code inside this should execute only after api call and assigment from the previous.
    <script src="https://test.com/script3.js"></script> // This also consumes data from first import
 </head>
<body>
 <div id="body></div>
</body>
</html>

Solution

  • Short answer ...

    "There is no way around async/await syntax, unless one wants to fall back to purely written promises."

    From both of my above comments ...

    Your prototypal async getConfigData method does neither assign anything to an instance's own dynamicData property, nor does it return any value. But instead it does assign an API response value to an instance's own dynamicUrl property. My advice getConfigData should never assign anything to an instance's property, but always return a value that can be awaited ...

    ... e.g. like that ...

    class GlobalConstants {
      async getConfigData() {
        let result = {};
    
        try {
          const configResponse =
            await fetch("https://jsonplaceholder.typicode.com/todos/1");
    
          if (configResponse.status === 200) {
    
            result = await configResponse.json();
          } else {
            throw new Error(`Config API threw ${formattedConfigResponse.status}`);
          }
        } catch (error) {
          console.warn("Failed to fetch:", error);
        }
        return result;
      }
    }
    
    /**
     *  quoted from MDN:
     *
     *  "The await operator is used to wait for a
     *   Promise and get its fulfillment value.
     *   It can only be used inside an async function
     *   or at the top level of a module."
     */
    (async() => {
    
      window.globalConstants = {};
    
      const configData = await new GlobalConstants().getConfigData();
    
      Object.assign(window.globalConstants, configData);
    
      console.log({ globalConstants: window.globalConstants });
      
      // Note.
      //
      // - execute the depended JavaScript code here
      //    - either by invoking an application's `main` or `init` function,
      //    - or by simply pasting all the depended code beneath here,
      //    - or by dynamically importing an/other source/s.
    
      /* await */import('https://source/path/source-name.js');
    })();
    
    // proves the async running code.
    console.log({ globalConstants: window.globalConstants });
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    ... In addition, why would one choose a class based abstraction at all. It looks like there will be anyway just one instance of it, with getConfigData too, seeing a one time use only. Why not making the latter a single globally or module scoped function which retrieves the data that needs to be awaited. There is no way around async/await.

    .as-console-wrapper { min-height: 100%!important; top: 0; }
    <!DOCTYPE HTML>
    <html>
      <head>
        <!-- This contains the code which was shared in the Q //-->
        <!-- <script src="https://test.com/script1.js"></script> //-->
    
        <!--
          This consumes the data. Hence code inside this should
          execute only after api call and assigment from the previous.
        //-->
        <!-- <script src="https://test.com/script2.js"></script> //-->
    
        <!-- This also consumes data from first import //-->
        <!-- <script src="https://test.com/script3.js"></script> //-->
    </head>
    <body>
      <div id="body"></div>
      <script>
    
        // instead of  ... <script src="https://test.com/script1.js"><\/script>
        // ... do ...
    
        async function getConfigData() {
          let result = {};
    
          try {
            const configResponse =
              await fetch("https://jsonplaceholder.typicode.com/todos/1");
    
            if (configResponse.status === 200) {
    
              result = await configResponse.json();
            } else {
              throw new Error(
                `Config API threw ${formattedConfigResponse.status}`
              );
            }
          } catch (error) {
            console.warn("Failed to fetch:", error);
          }
          return result;
        }
    
        (async() => {
    
          window.globalConstants = {};
    
          const configData = await getConfigData();
    
          Object.assign(window.globalConstants, configData);
    
          console.log({ globalConstants: window.globalConstants });
    
          // instead of ... 
          // ... <script src="https://test.com/script2.js"><\/script>
          // ... <script src="https://test.com/script3.js"><\/script>
          // ... do ...
    
          /* await */import('https://test.com/script2.js');
    
          import("https://test.com/script3.js")
            // .then(module => {
            //   // whatever.
            // })
            // .catch(err => {
            //   // handle error.
            // });
    
        })();
    
        // proves the async running code.
        console.log({ globalConstants: window.globalConstants });
    
      </script>
    </body>
    </html>

    The above shown/mentioned dynamic import gets achieved via the import syntax.