Why is synchronous sleep function not made async by being inside promise?

I'm trying to wrap my head around promises, and how JavaScript works with it's queue and event loop etc.

I thought that if I put a slow synchronous function inside a promise, that slow sync function would be delegated to the background and I could use .then to deal with it when it was done.

function syncSleep(ms){
    var end = new Date().getTime() + ms;
    var start = new Date().getTime();

    while (start < end) {
      start = new Date().getTime();
    }
}

function p() {
  return new Promise(function(resolve) {
     syncSleep(5000);
     resolve("syncSleep done!");
  });
}

p().then( function(s) {
  var div = document.getElementById('async');
  div.innerHTML = s;
} );

var div = document.getElementById('sync');
div.innerHTML = "This should appear right away! (but it doesn't)";

https://jsfiddle.net/7mw6m2x5/

The UI is unresponsive while this code runs though.

So I was wondering, can someone explain what is going on here? Are Promises only a way to handle code that is already "made to be" async?

(If so, how is that done?)

How do I deal with slow sync code when I don't want it to freeze the UI? Do I have to use a web worker for that?

Grateful for any clarification. Thanks.

Answers:

Answer

The code works as expected.

Since Javascript is single threaded, the UI will block while your loop is executing.

Promises are just a nice way to handle async code. See an introduction here:

http://www.html5rocks.com/en/tutorials/es6/promises/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

To be able to maintain the UI reponsive while other code is executing in the background, you would need to use WebWorkers:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

Quoting from the page listed above:

"Web Workers provide a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface."

Update:

following your comments, I crafted this little script to demonstrate the difference between blocking and no-blocking approaches. There is some repetition in the code, but I think it is simple enough to understand.

function setVal(s) {
  var divAsync = document.getElementById('async');
  var innerDiv = document.createElement('div');
  innerDiv.innerHTML = s + '<br>';
  divAsync.appendChild(innerDiv);
}


function syncSleep(ms) {
  var end = new Date().getTime() + ms;
  var now = new Date().getTime();
  var stepBegin = new Date().getTime();
  var step = 0;

  // This loop is blocking
  // The UI will only refresh after the loop completion
  while (now < end) {
    now = new Date().getTime();
    step = now - stepBegin;
    if (step >= 1000) {
      setVal(now);
      step = 0;
      stepBegin = now;
    }
  }

}

function pBlock() {
  return new Promise(function(resolve) {
    syncSleep(3200);
    resolve("pBlock syncSleep done!");
  });
}


function noBlockUpdate(ms, resolve) {
  var end = new Date().getTime() + ms;
  var now = new Date().getTime();
  var stepBegin = new Date().getTime();
  var step = 0;

  function noBlock() {
    now = new Date().getTime();

    // paint every 1000ms;
    step = now - stepBegin;
    if (step >= 1000) {
      setVal(now);
      step = 0;
      stepBegin = now;
    }

    if (now < end) {
      // NB: this is going to be called thousands of times
      // But the UI will still update every 1000 ms
      setTimeout(noBlock);
    } else {
      resolve("pNoBlock done!");
    }
  };
  noBlock();
}

function pNoBlock() {
  return new Promise(function(resolve) {
    noBlockUpdate(3200, resolve);
    setVal("pNoBlock launched!");
  });
}



pNoBlock().then(setVal);

var divSync = document.getElementById('sync');
divSync.innerHTML = "This appears right away!";



// Just wait 4 seconds so the non-blocking code completes
setTimeout(function() {
  // Clear all div's
  document.getElementById('sync').innerHTML = '';
  document.getElementById('async').innerHTML = '';

  var divSync = document.getElementById('sync');
  divSync.innerHTML = "This does not appear right away, only after the blocking operation is complete!";

  pBlock().then(setVal);
}, 4000);
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>

<body>

  <div id="sync"></div>

  <div id="async"></div>

</body>

</html>

Answer

Are Promises only a way to handle code that is already 'made to be' async?

Yes. Promises don't truly create asynchronous operations. Their intention is just to make working with asynchronous operations easier, by defining a consistent API. But, they don't avoid blocking on their own beyond one small delay between when resolve() and .then() callbacks are invoked.

The function provided to the Promise constructor is invoked immediately and synchronously. And, once a .then() callback begins executing, it will have to run to completion before the engine does anything else.

How do I deal with slow sync code when I don't want it to freeze the UI?

Try to avoid long-running synchronous operations as much as possible. An asynchronous alternative to syncSleep() would be setTimeout().

function p() {
  return new Promise(function(resolve) {
    setTimeout(function () {
      resolve("syncSleep done!");
    }, 5000);
  });
}

If a long-running synchronous operation can't be avoided, then you'll want to try to move it to a separate process / instance of the engine – and, in browsers, that can be done with Web Workers.

A possible middle ground may be, if you can break up the operation into multiple steps, to separate each step by a brief setTimeout(). Those breaks will give the engine time periodically to work on other events, including updating the page for the user. You'll want each step to be small to have as many breaks as often as possible, since each step will still block everything else once it starts.

Answer

Promises are not threads. They're just a sugar for handling success and failure events (callbacks) in single-threaded code.

Callback to new Promise(cb) constructor is executed immediately and synchronously. Callbacks given to .then(cb)/.catch(cb) are executed on next tick after the promise is resolved/rejected, but they also run on the same—and only—thread.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.