javascriptasync-awaitfetch-apisettimeoutalert

How to pause javascript code execution until fetch() actually gets the file


The Scenario

Let's say the progression of tasks is triggered with a window load like this,

window.addEventListener("load", doTheseFirst, { once: true });

function doTheseFirst()
{
  // Show some pictures and play some sounds for 3 seconds
  setTimeout(thenDoThese, 3000);
}

function thenDoThese()
{
  // Show other pictures and play other sounds for 5 seconds
  setTimeout(andThenDoTheseToo, 5000);
}

function andThenDoTheseToo()
{
  // Maybe display some kind of option which will
  // allow the user to choose how to proceed further
  someButtonElement.addEventListener("click", whenButtonIsClicked);
}

function whenButtonIsClicked()
{
  // etc etc
  // You get the idea
}

And let's say we have a fetch() that fires practically at the same time with either window load or the addition of the event listener.
An example of which could look like this,

fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ /*What to do when the file is ready*/ });
// Assume that download speed is unpredictable
window.addEventListener("load",function(){   doTheseFirst();   }, { once: true });
// ...
// ...

In this case the time of completion for fetch() is totally UNCERTAIN i.e. we do not know at what exact moment fetch() will actually get the file. Therefore the situation resembles a moment like the one seen in the image,

setTimeout versus fetch

And yet what if we must PAUSE the task chain as soon as fetch() is done and ready with the file and then UNPAUSE it after some kind of dialog box or notification msg is displayed.

The easy solution for that is using an alert box since it blocks/pauses the code execution automatically and respects the remaining milliseconds in whichever setTimeout was ticking at that moment. The following code makes the alert box pop up as soon as fetch() gets the content from the txt file. Recall that fetch() and window load are working independently.

fetch("somefolder/notification.txt").then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ alert(contentOfTheTxtFile); });

This works because when the alert box is closed, the main code goes back to its normal flow with a perfect UNPAUSING effect, just as needed.

So the question is,

How do we do the same thing without using a crude alert box that we cannot assign any styles to? Can we make it so that the code execution is paused as soon as fetch() gets the file and then it is unpaused immediately once the user clicks something like an OK or NEXT button?

In other words, is it possible to mimic the behavior of alert() ?


NOTICE: This particular research has been initiated during the development of SPEAKWORLDLANGUAGES app


Solution

  • fetch is asynchronous, which basically means

    fetch('some_folder/some_file.xyz').then();
    // Here at this point fetch has just started looking for the file and will be busy trying to get it
    // And yet
    console.log("This will immediately show regardless of how long it takes for fetch to complete");
    

    There are two ways to make it act as if it was synchronous which means delaying all following tasks until fetch is complete. The first one is,

    1. When using .then() notation call the next task handler function in the function chain via .finally()

    Like

    fetch('some_folder/some_file.txt').then(response => {return response.text();})
      .then(text => { /*Do what you want with the contents of the txt file*/ })
      .catch(error => { console.error('fetch could not get the file', error); })
      .finally(whenFetchIsDone);
    
    function whenFetchIsDone() {
      console.log("This will show only after fetch completes either with or without success");
      // Here we can call a function like goAheadAndPerformTheNextTaskInLine();
    }
    

    The other way is

    1. Making use of await inside an async function with a try catch finally block

    Like

    async function fetchData() {
      try {
        const response = await fetch('some_folder/some_file.txt');
        if (!response.ok) {  throw new Error('fetch has failed');  }
        const theText = await response.text();
        // Do what you want with theText
      } catch (error) {
        console.error('could not read file', error);
      } finally {
        console.log("This will show only after fetch completes either with or without success");
        // Here we can call a function like goAheadAndPerformTheNextTaskInLine();
      }
    }
    fetchData();
    

    Note that by using either method it is possible to chain multiple fetch tasks so that files will be downloaded one by one if necessary.