skip to Main Content

I want to verify the user credentials before any page of my site load, so I put an script in the top of each of my HTML document. But this script in particularly has Async functions (because it sends API calls for the verification), and I noticed that other elements load before this script when some Async function has to be done (inside of this particularly script), even I putting at the top of the HTML.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap" rel="stylesheet">
    <link href="home.css" rel="stylesheet">
    <script src="../isUserLogged.js"></script> <!--This is the script that I mentioned-->
    <script src="home.js" defer></script> <!--This script is running before even with "DEFER" annotation-->
    <title>Caderneta</title>
</head>
<body>
    <div class="header">
        <p id="home-txt" class="header-txt">Home</p>
        <p id="user-txt" class="header-txt">Olá, Nome!</p>
    </div>
    <div class="content">
        <iframe src="../notebooks/notebooks.html"></iframe>
    </div>
</body>
</html>

"isUserLogged.js":

//Local Storage
const token = localStorage.getItem("token");

async function isUserSessionValid() {
    const options = {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "Authorization": token
        },
        body: token
    };
    try {
        const response = await fetch("http://localhost:8080/teachers/get-by-token", options);
        if(response.ok) {
            sessionStorage.setItem("user", JSON.stringify(await response.json()));
            return true;
        }
    } catch(e) {
    }
    alert("Erro ao validar sessão, faça login ou tente novamente mais tarde.");
    return false;
}

async function validateUserSession() {
    if(token != null) {
        if(await isUserSessionValid()) {
            return;
        }
    }
    localStorage.clear();
    sessionStorage.clear();
    location.replace("../login/login.html");
}

validateUserSession();

What can I do to the elements just load after all async functions has fineshed?

2

Answers


  1. According to the documentation the script with the defer attribute will be executed after parsing the document but NOT after rendering the document. The reason, why you feel like the script executed before is a Rendering Delay.

    As @Phil mentioned in the comment, You have to do some workarounds like Adding a loading screen and hiding it when your function execution is completed.

    <body>
        <div id='loader' class='loader'>Loading...</div>
        <!-- Rest of your code -->
    </body>
    
    .loader {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    async function validateUserSession() {
        if(token != null) {
            if(await isUserSessionValid()) {
                const loader = document.getElementById('loader');
                loader.style.display = 'none';
                return;
            }
        }
        localStorage.clear();
        sessionStorage.clear();
        location.replace("../login/login.html");
    }
    
    Login or Signup to reply.
  2. You are trying to delay HTML parsing using an async function, that won’t work.

    First you need to understand what defer is ‘deferring’ wrt. Defer is akin to putting a regular script right at the bottom of your page. This means, deferred scripts will ALWAYS fire right before DOMContentLoaded event of your window. Any promises or async scripts inside prior regular script will not stop parsing HTML further.

    DOMContentLoaded means you can fully see the initial HTML text with placeholders for images etc., without guarantee of imgs, videos, dynamic stylesheets, links and async scripts. Once all of these are loaded, a separate load event fires. So, load comes after DOMContentLoaded.

    Async scripts that come with the page give you 0 guarantee. They must fire before load but some can also fire before DOMContentLoaded. The async attribute essentially offloads downloading of these assets to a separate thread.

    At this point you have 4 choices:

    1. Make a dedicated HTML page that just runs isUserLogged.js and the server responds with a Content-type: text/html resembling a loggin page.
    2. Visually hide the elements using css until your async function returns truthy or login detais. This is your current solution. Set styles inside validateUserSession to hide what home.js operates on.
    3. Inline the contents of home.js in a function and execute within validateUserSession.then(...)
    4. Make all the scripts async and force execution order.

    If you want to go with 4th choice, my solution would be to do something like this:

    in your html:

    <head>
        <script src="https://cdn.jsdelivr.net/npm/@ibowankenobi/taskq/taskq.js"></script>
        ...
        <script src="../isUserLogged.js" async></script> 
        <script src="home.js" async></script> 
    </head>
    

    Pay attention that both scripts now async and there is an additional regular taskq script at the top.

    Now with a bit of change you can have the effect you want, essentially wrap your existing functions with a iife (immediately invoked function expression) and add a few props.

    !function(){
      function logMe(){
        const token = localStorage.getItem("token");
        const someObj = {value: null};
        async function isUserSessionValid() {...}
        async function validateUserSession() {
          taskq.pause;
          if(token != null) {
            if(await isUserSessionValid()) {
                someObj.value = token;
                taskq.resume;
                return;
            }
          }
          ...
        }
        validateUserSession();
        taskq.export(someObj, "tokenObj");
      }
      logMe._taskqId = "first"
      taskq.push(logMe)
    }()
    

    I put everything inside isUserLogged to a function called logMe and wrapped it within an iife. I gave it a _taskqId of first and pushed it into execution flow. I paused/resumed taskq within validate function. I additionally exported a variable which you can use down the line.

    Essentially the same for home.js:

    !function(){
      function home(tokenObj){
        //contents of your home.js
        ...
      }
      home._taskqId = "home";
      home._taskqWaitFor = ["first"]
    }()
    

    Above function will wait for first to execute. The tokenObj we exported earlier is also available inside home, so tokenObj.value is your user’s token. And that’s it.

    You could also have used taskq.load("./home.js") inside isUserLogged.js instead of taskq.pause/taskq.resume, you have several options.

    Note that this solution will work on ie10 included and can easily be combined with modern es6 modules as well. You can always roll your own solution similar to taskq and they all revolve around storing functions prior to execution.

    Disclaimer: I am the developer of taskq.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search