0

I am aware Javascript is single-threaded. However I do not understand why the following code does not show/hide a spinner before/after a compute-intensive task.

Code outline:

showSpinner();
computeIntensiveTask();
hideSpinner();

The code (I am using the Bootstrap spinner)

const showSpinner = () => {
    const $spinner = $('#spacewalk-spinner').find('.spinner-border');
    $spinner.show();
};

const hideSpinner = () => {
    const $spinner = $('#spacewalk-spinner').find('.spinner-border');
    $spinner.hide();
};

The function computeIntensiveTask() does a ton of sqrt() and other 3D vector math.

When the code runs, the spinner never appears. Why is this happening?

UPDATE 0

As a test I tried simply updated the spinner elements color before/after:

before document.getElementById('spacewalk-spinner').style.color = 'rgb(255,0,0)';

after document.getElementById('spacewalk-spinner').style.color = 'rgb(0,255,0)';

and only the 'after' color change took place.

UPDATE 1

To clarify. If I remove the call to hideSpinner() and change showSpinner() to document.getElementById('spacewalk-spinner').style.display = 'block'. The spinner shows after computeIntensiveTask() completes. This despite the fact I have placed computeIntensiveTask() within call to window.setTimeout with a 500 ms wait.

dugla
  • 12,774
  • 26
  • 88
  • 136

2 Answers2

1

You need to coordinate the updating of the UI by using a setTimeout function. Also you need to position the showSpinner and hideSpinner functions in relation to the updating of the UI. See this snippet as an example.

const showSpinner = () => {
  const $spinner = $('#spacewalk-spinner').find('.spinner-border');
  $spinner.show();
  console.log('show');
};

const hideSpinner = () => {
  const $spinner = $('#spacewalk-spinner').find('.spinner-border');
  $spinner.hide();
  console.log('hide');
};


const computeIntensiveTask = () => {
  showSpinner();
  // begin the calculations after the UI updates by using setTimeout
  setTimeout(function() {
    for (var start = 1; start < 1000; start++) {
      // calculating...
      console.log('calc');
    }
    hideSpinner();
  }, 0);

};

computeIntensiveTask();
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="spacewalk-spinner">
  <div class="spinner-border" role="status">
    <span class="sr-only">Loading...</span>
  </div>
</div>
ctaleck
  • 1,658
  • 11
  • 20
  • Was it your intent to use zero (0) for the setTimeout param? If so I don't see how this code is any different - other then embedding the hide/show functions in computeIntensiveTask() - this my example. My fundamental question remains: Why is a function called prior to computeIntensiveTask() not happening until after computeIntensiveTask() completes. This should work in a single-threaded context with no need for a timeout. – dugla Sep 04 '19 at 20:32
  • I think your understanding of "single-threaded context" is not accurate. The UI will __not update__ until the thread completes. The `setTimeout` causes the UI to update--your code will not update the UI until the __all the code completes__. – ctaleck Sep 04 '19 at 20:43
  • Yes, zero is the correct number as we want the the new thread to start immediately. – ctaleck Sep 04 '19 at 20:51
  • See MDN for more about what is happening with `setTimeout(fn,0)` https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Zero_delays – ctaleck Sep 04 '19 at 21:01
  • Bingo! Works like a charm. Cheers. – dugla Sep 04 '19 at 22:01
  • Just curious, did you try the __Run code snippet__ button above? – ctaleck Sep 05 '19 at 02:55
  • I just stared at the code for a bit. It does work. :-) It's clear I need to dig into to the JS event loop model and get a better understanding of it all. I've been using spinners forever with async network tasks. I had no idea the approach for compute intensive tasks required a different approach. – dugla Sep 05 '19 at 11:58
-1

try this:

showSpinner();
setTimeout(() => {
    computeIntensiveTask();
    hideSpinner();
}, 300);