Search code examples
javascripthtmlasynchronousasync-awaitpromise

How to prevent the page from loading until a script in the head with promises resolve


I have an external asynchronous script needs to finish executing before the page loads. To my understanding (from this thread), the default behaviour before the page starts to load is that it finishes rendering the head tag's contents, and if it includes a script, it finishes parsing that too, before proceeding to load the page. However, I could be wrong, but this seems to not apply to asynchronous scripts.

How could I make this example asynchronous script below finish executing (and all promises finishing) before the page starts to load?

The page, including the script:

Playground: https://playcode.io/1782092

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>p {color: blue}</style>

    <script>
  // Function to create a fake promise that resolves after 3 seconds
  async function fakePromise() {
    return new Promise(function(resolve, reject) {
      setTimeout(resolve, 3000); // Resolve after 3 seconds
    });
  }

  // Use async/await to wait for the fake promise to resolve
  async function applyStyle() {
    await fakePromise();

    // Create a <style> element
    var styleElement = document.createElement('style');

    // Define the CSS rule
    var css = "p { color: pink !important; }";

    // Add the CSS rule to the <style> element
    styleElement.appendChild(document.createTextNode(css));

    // Insert the <style> element into the <head> of the document
    document.head.insertBefore(styleElement, document.head.firstChild);
  }

  // Call the function to apply the style
  applyStyle();

  // *** in the REAL app, this script would just have empty contents and have a src attribute ***
</script>

  </head>
  <body>
    
    <p>testing</p>

  </body>
</html>

As you can see, the example 'testing' text changes colour after 3 seconds, but the problem is that this happens after the page has loaded. The blue 'testing' text should never appear, because the promise wouldn't have resolved by then, and when it is resolved, it changes to pink, hence you should only see pink text.

If this is not possible, how else could I, for example, redirect to another page after the asynchronous script finishes but before the page starts to load?


Solution

  • If you block the page from loading, you are forcing your end users to wait without feedback: you won't be able to show a loading spinner or anything else, and the user will perceive the delay as a frozen page or slow server. This is why a lot of attention is spent on minimizing the critical rendering path, which Google (at least) uses as some of the Core Web Vitals, which are reportedly included in page ranking signals*.

    Generally speaking, the good move is to transition synchronous resources to asychronous resources, allowing your page to render as soon as possible even if the content it loads appears shortly thereafter. Though this won't necessarily help with "contentful paints" in Core Web Vitals, it will provide a more positive user experience.

    If you need to run an A/B test, you might find it more effective to deliver the A/B test status as a server-side calculation, or to stay with your loading spinner behavior and try to optimize or cache the API calculation as much as possible.


    *Disclaimer: I am a Google employee, but do not and have not worked on page ranking algorithms. I am speaking only for myself based only on publicly available documentation.