简体   繁体   中英

Why there is no sleep functionality in javascript when there is setTimeout and setInterval?

Why there no such function in javascript that sets a timeout for its continuation, saves the necessary state (the scope object and the execution point), terminates the script and gives the control back to the browser? After the timeout expires the browser would load back the execution context and continues the script, and we would have a real non browser blocking sleep functionality that would work even if the JS engine is single threaded.

Why there is still no such functionality in javascript? Why do we have to still slice our code into functions and set the timeouts to the next step to achieve the sleep effect?

I think 'sleep'ing is something you do not want in your browser.

First of all it might be not clear what has to happen and how a browser should behave when you actually sleep.

  • Is the complete Script runtime sleeping? Normally it should because you only have one thread running your code. So what happens if other events oocur during sleep? they would block, and as soon execution continues all blocked events would fire. That will cause an odd behaviour as you might imagine (for instance mouse click events which are fired some time, maybe seconds, after the actual click). Or these events had to be ignored, which will lead to a loss of information.

  • What will happen to your browser? Shall it wait for sleep if the user clicks a (eg close window) button? I think not, but this might actually call javascript code again (unload) which will not be able to be called since program execution is sleeping.

On a second thought sleep is a sign of poor program design. Actually a program/function/you name it has a certain task, which shall be completed as soon as possible. Sometimes you have to wait for a result (for instance you wait for an XHR to complete) and you want to continue program execution meanwhile. In this case you can and should use asynchronous calls. This results in two advantages:

  • The speed of all scripts is enhanced (no blocking of other scripts due to sleep)
  • The code is executed exactly when it should and not before or after a certain event (which might lead to other problems like deadlocks if two functions check for the same condition...)

... which leads to another problem: Imagine two or more pieces of code would call sleep. They would hinder themselves if they try to sleep at the same, maybe unnecessarily. This would cause a lot of trouble when you like to debug, maybe you even have difficulties in ensuring which function sleeps first, because you might control this behavior somehow.

Well I think that it is one of the good parts of Javascript, that sleep does not exist. However it might be interesting how multithreaded javascripts could perform in a browser;)

javascript is desgined for single process single thread runtime, and browser also puts UI rendering in this thread, so if you sleep the thread, UI rendering such as gif animation and element's event will also be blocked, the browser will be in "not responding" state.

Maybe a combination of setTimeout and yield would work for your needs?

What's the yield keyword in JavaScript?

You could keep local function scope while letting the browser keep going about its work.

Of course that is only in Mozilla at the moment?

It sounds like what you're looking for here is a way to write asynchronous code in a way that looks synchronous. Well, by using Promises and asynchronous functions in the new ECMAscript 7 standard (an upcoming version of JavaScript), you actually can do that:

// First we define our "sleep" function...
function sleep(milliseconds) {
  // Immediately return a promise that resolves after the
  // specified number of milliseconds.
  return new Promise(function(resolve, _) {
    setTimeout(resolve, milliseconds);
  });
}

// Now, we can use sleep inside functions declared as asynchronous
// in a way that looks like a synchronous sleep.
async function helloAfter(seconds) {
  console.log("Sleeping " + seconds + " seconds.");
  await sleep(seconds * 1000); // Note the use of await
  console.log("Hello, world!");
}

helloAfter(1);
console.log("Script finished executing.");

Output:

Sleeping 1 seconds.
Script finished executing.
Hello, world!

( Try in Babel )

As you may have noticed from the output, this doesn't work quite the same way that sleep does in most languages. Rather than block execution until the sleep time expires, our sleep function immediately returns a Promise object which resolves after the specified number of seconds.

Our helloAfter function is also declared as async , which causes it to behave similarly. Rather than block until its body finishes executing, helloAfter returns a Promise immediately when it is called. This is why "Script finished executing." gets printed before "Hello, world.".

Declaring helloAfter as async also allows the use of the await syntax inside of it. This is where things get interesting. await sleep(seconds * 1000); causes the helloAfter function to wait for the Promise returned by sleep to be resolved before continuing. This is effectively what you were looking for: a seemingly synchronous sleep within the context of the asynchronous helloAfter function. Once the sleep resolves, helloAfter continues executing, printing "Hello, world." and then resolving its own Promise.

For more information on async/await, check out the draft of the async functions standard for ES7.

Because "sleep()" in JavaScript would make for a potentially horrible user experience, by freezing the web browser and make it unresponsive.

What you want is a combination of yield and Deferreds (from jquery for example).

It's called sometimes pseudoThreads, Light Threading or Green Threads. And you can do exactly what you want with them in javascript > 1.7. And here is how:

You'll need first to include this code:

$$ = function (generator) {
    var d = $.Deferred();
    var iter;
    var recall = function() {
       try {var def = iter.send.apply(iter, arguments);} catch(e) {
          if (e instanceof StopIteration) {d.resolve(); return;}
          if (e instanceof ReturnValueException) {
              d.resolve(e.retval); return
          };
          throw e;
       };
       $.when(def).then(recall);      // close the loop !
    };
    return function(arguments) {
         iter = generator.apply(generator, arguments);
         var def = iter.next();        // init iterator
         $.when(def).then(recall);     // loop in all yields
         return d.promise();           // return a deferred
    }
}

ReturnValueException = function (r) {this.retval = r; return this; };
Return = function (retval) {throw new ReturnValueException(retval);};

And of course call jquery code to get the $ JQuery acces (for Deferreds).

Then you'll be able to define once for all a Sleep function:

function Sleep(time) {
  var def = $.Deferred();
  setTimeout(function() {def.resolve();}, time);
  return def.promise();
}

And use it (along with other function that could take sometime):

// Sample function that take 3 seconds to execute
fakeAjaxCall = $$(function () {
   yield (Sleep(3000));
   Return("AJAX OK");
});

And there's a fully featured demo function:

function log(msg) {$('<div>'+msg+'</div>').appendTo($("#log")); }

demoFunction = $$(function (arg1, arg2) {
   var args = [].splice.call(arguments,0);
   log("Launched, arguments: " + args.join(", "));
   log("before sleep for 3secs...");
   yield (Sleep(3000));
   log("after sleep for 3secs.");

   log("before call of fake AjaxCall...");
   ajaxAnswer = yield (fakeAjaxCall());
   log("after call of fake AjaxCall, answer:" + ajaxAnswer);

   // You cannot use return, You'll have to use this special return
   // function to return a value
   log("returning 'OK'.");
   Return("OK");
   log("should not see this.");
});

As you can see, syntax is a little bit different:

Remember:

  • any function that should have these features should be wrapped in $$(myFunc)
  • $$ will catch any yielded value from your function and resume it only when the yielded value has finished to be calculted. If it's not a defered, it'll work also.
  • Use 'Return' to return a value.
  • This will work only with Javascript 1.7 (which is supported in newer firefox version)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM