1

I’m trying to show a loading indicator when executing a long-running task. The loader should remain visible for the entire duration of the task and then be hidden when the task completes. However, the loader--show class is briefly applied and does not stay visible at all during the execution of the task.

Here’s a simplified version of my code: https://jsfiddle.net/p6ejkdcb/

<button id="test">
Do Something
</button>

<div id="loader" class="loader">
Loading...
</div>
const button = document.getElementById("test");
const loader = document.getElementById("loader");

test.addEventListener("click", (_) => {
  new Promise(function (resolve, reject) {
      console.log("showLoader");
      showLoader();
      resolve();
    })
    .then(function (result) {
      for (let i = 0; i < 500000; i++) {
        console.log("do the work");
      }
      return result;
    })
    .then(function (result) {
      console.log("hide the loader");
      hideLoader();
      return result;
    })
})

function showLoader() {
  loader.classList.add("loader--show");
}
function hideLoader() {
  loader.classList.remove("loader--show");
}
.loader {
  display: none;
  &--show {
    display: block;
  }
}

Expected behaviour:

  • The loader should become visible when I click the "Do Something" button.
  • It should remain visible while the code inside the promise's then() method executes.
  • Once the task is complete, the loader should be hidden.

Actual behaviour:

  • The loader briefly received the loader--show class but does not stay visible during the execution of the loop.
6
  • 4
    Promises are for managing code that is already asynchronous (via primitives such as setTimeout(), fetch(), etc.). Your for loop is synchronous and blocking, not asynchronous, so your Promise won't do much here. Commented Oct 8, 2024 at 11:27
  • 3
    In addition to what Nick Parsons said, do you realize that your for loop is going to execute all 1000 iterations on the order of a few milliseconds? Computers are fast. You aren't going to be able to visibly see that very much even if your code is working as intended. Commented Oct 8, 2024 at 11:29
  • See: Correct way to write a non-blocking function in Node.js Commented Oct 8, 2024 at 11:33
  • I only used a for loop as a simplification to show that something is happening at that point. In reality it's some pretty weighty DOM interactions (hence why a Worker isn't an option as well) - not ideal but it is what it is. @NickParsons thanks for the link, it looks like the answers there generally use an arbitrary setTimeout to change "when" the code runs. Funnily enough that was what I initially doing but it felt like there must be a better way. Commented Oct 8, 2024 at 11:48
  • @ckinte well showing the loader is a dom interaction as well. You need a (small) timeout to tell the browser to render that before continuing with the "pretty weighty DOM interactions". Commented Oct 8, 2024 at 12:24

3 Answers 3

0

Your imitation of long running task is incorrect. With your for loop you are just hanging main browser thread that also responsible for rendering changes to the DOM.

If you want to imitate correctly, just use something like this:

const longRunningTask = (duration) => new Promise(
    (resolve) => setTimeout(() => resolve(), duration),
);

just replace your .then with this:

.then(function (result) {
    return longRunningTask(3000).then(function () { return result; });
})

and it will work as you desire. I did it for you in copy of your fiddle: https://jsfiddle.net/aw9rqL8s/

Sign up to request clarification or add additional context in comments.

Comments

-3

When you add classlist with name --loader--show

But in your css You use class name as separate Lile --loader{ &--show }

Use like this --loader--show{ Write css }

1 Comment

OP is using SCSS, so those classes actually exists.
-3

You just need to hide your loader right after the resolve() call inside the Promise.

const myLoader = document.querySelector('#loader');

function closeLoader ()
{
  myLoader.style.display = 'none';
}

function openLoader ()
{
  myLoader.style.display = 'block';
}

function doHeavyWork ()
{
  // Show the loader.
  openLoader();
  
  const myPromise = new Promise
  (
    ( resolve, reject ) =>
    {
      // Do your heavy work here.
      console.log('Heavy work started.');
      
      // Simulating some heavy work.
      setTimeout
      (
        () =>
        {
          console.log('Heavy work stopped.');
          
          // Resolve the Promise.
          resolve();
        },
        3000 // 3 seconds wait.
      );
    }
  );
  
  // Do some real heavy work.
  const newPromise = myPromise.then
  (
    () =>
    {
      for ( var index = 0; index < 5000000; index++ ) {}
    }
  );
  
  // Do the final work.
  const finalPromise = newPromise.then
  (
    () =>
    {
      closeLoader();
    }
  )
}
#loader
{
  display: none;
}
<button onclick='doHeavyWork()'>Do Something</button>

<div id='loader'>Loading...</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.