60

I have a code which needs to be executed after some delay say 5000 ms.Currently I am using setTimeout but it is asynchronous and i want the execution to wait for its return. I have tried using the following:

function pauseComp(ms) 
 {
     var curr = new Date().getTime();
     ms += curr;
     while (curr   < ms) {
         curr = new Date().getTime();
     }
 } 

But the code i want to delay is drawing some objects using raphaeljs and the display is not at all smooth. I am trying to use doTimeout plugin. I need to have a delay only once as the delay and code to be delayed are both in a loop. I have no requirement for a id so I am not using it. For example:

for(i; i<5; i++){ $.doTimeout(5000,function(){
         alert('hi');  return false;}, true);}

This waits for 5 sec befor giving first Hi and then successive loop iterations show alert immediately after the first. What I want it to do is wait 5 sec give alert again wait and then give alert and so on.

Any hints/ suggestions are appreciated!

PG1
  • 1,220
  • 2
  • 12
  • 27
kavita
  • 739
  • 2
  • 6
  • 7

12 Answers12

79

Variation on the accepted answer which is just as good as this one.

Also, I agree with the caveats of preferring setTimeout and asynchronous function calling but sometimes e.g., when building tests, you just need a synchronous wait command...

function wait(ms) {
    var start = Date.now(),
        now = start;
    while (now - start < ms) {
      now = Date.now();
    }
}

if you want it in seconds, divide start ms by 1000 on the while check...

=== EDIT ===

I noticed that my answer has bubbled to the top but it really shouldn't be the top answer. That was written as an alternative in case you cannot use async / await in your code or you're waiting for a trivial amount of time (like a second or two for testing).

The top answer should note that the async/await pattern is a much better way of doing this and will significantly use less energy and CPU cycles.

See @michaelolof 's answer below for example....

const wait = (msec) => new Promise((resolve, _) => {
  setTimeout(resolve, msec));
});

(async () => {
  console.log("Start...")
  await wait(5000);
  console.log("...End")
})();
ThinkBonobo
  • 15,487
  • 9
  • 65
  • 80
37

If you'd like to take advantage of the new async/await syntax, You can convert set timeout to a promise and then await it.

function wait(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("Done waiting");
      resolve(ms)
    }, ms )
  })
}  

(async function Main() {
  console.log("Starting...")
  await wait(5000);
  console.log("Ended!")
})();
Adrian W
  • 4,563
  • 11
  • 38
  • 52
michaelolof
  • 379
  • 3
  • 5
35

Synchronous wait (only for testing!):

const syncWait = ms => {
    const end = Date.now() + ms
    while (Date.now() < end) continue
}

Usage:

console.log('one')
syncWait(5000)
console.log('two')

Asynchronous wait:

const asyncWait = ms => new Promise(resolve => setTimeout(resolve, ms))

Usage:

(async () => {
    console.log('one')
    await asyncWait(5000)
    console.log('two')
})()

Alternative (asynchronous):

const delayedCall = (array, ms) =>
    array.forEach((func, index) => setTimeout(func, index * ms))

Usage:

delayedCall([
    () => console.log('one'),
    () => console.log('two'),
    () => console.log('three'),
], 5000)
Community
  • 1
  • 1
Lin
  • 1,041
  • 10
  • 6
10

Using the new Atomics API, you can start synchronous delays without performance spikes:

const sleep = milliseconds => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds)

sleep(5000) // Sleep for 5 seconds

console.log("Executed after 5 seconds!")
Richie Bendall
  • 7,738
  • 4
  • 38
  • 58
  • This has the very beneficial effect of using an optimized thread sleep (instead of busy waiting), so the CPU can idle for the synchronous sleep duration instead of crunching a loop. However, keep in mind that the current specification does not allow `Atomics.wait` to be used on the main thread, only worker threads. – John Weisz Dec 16 '20 at 14:23
  • One possible problem is "If waiting is not allowed in the calling agent then it throws an Error exception. (Most browsers will not allow wait() on the browser's main thread.)": https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics Can't test myself since my browsers don't have `SharedArrayBuffer` yet. – Ciro Santilli OurBigBook.com Jan 03 '22 at 20:48
5

JavaScript is a single-threaded language. You cannot combine setTimeout and synchronous processing. What will happen is, the timer will lapse, but then the JS engine will wait to process the results until the current script completes.

If you want synchronous methods, just call the method directly!

If you want to process something after the setTimeout, include it or call it from the timeout function.

OverZealous
  • 39,252
  • 15
  • 98
  • 100
3

Non-timeout loops (that check the time or count to 1000000 or whatever) just lock up the browser. setTimeout (or the $.doTimeout plugin) is the best way to do it.

Creating timeouts within a loop won't work because the loop doesn't wait for the previous timeout to occur before continuing, as you've discovered. Try something more like this:

// Generic function to execute a callback a given number
// of times with a given delay between each execution
function timeoutLoop(fn, reps, delay) {
  if (reps > 0)
    setTimeout(function() {
                 fn();
                 timeoutLoop(fn, reps-1, delay);
               }, delay);
}

// pass your function as callback
timeoutLoop(function() { alert("Hi"); },
            5,
            5000);

(I just cobbled this together quickly, so although I'm confident that it works it could be improved in several ways, e.g., within the "loop" it could pass an index value into the callback function so that your own code knows which iteration it is up to. But hopefully it will get you started.)

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • 1
    Beside setTimeout is a better option, the OP request a syncronous method. @ThinkBonobo answered correctly – robsonrosa Mar 20 '17 at 14:12
  • 1
    @robsonrosa - The OP also said "the code i want to delay is drawing some objects using raphaeljs and the display is not at all smooth". You can't use synchronous code *and* draw things, because the screen won't be repainted until the synchronous code completes. Just because the OP asked for something doesn't make it possible. – nnnnnn Mar 20 '17 at 21:03
  • 1
    The user is specifically looking for a synchronous solution - setTimeout is not synchronous – Drenai Apr 18 '18 at 14:34
  • @Ryan - As per my previous comment, just because they asked for something doesn't make it possible. A synchronous delay can't coexist with the drawing they're doing, which they complained is not smooth enough. – nnnnnn Apr 18 '18 at 22:46
2

I have made a simple synchronous timeout function. It works in two different ways, callback and non-callback.

function:

function wait(ms, cb) {
  var waitDateOne = new Date();
  while ((new Date()) - waitDateOne <= ms) {
    //Nothing
  }
  if (cb) {
    eval(cb);
  }
}

callback example:

wait(5000,"doSomething();");

non-callback example:

console.log("Instant!");
wait(5000);
console.log("5 second delay");
  • 4
    Use `cb()` instead of `eval(db)`. Saving script in a string is not a good practice. Usage: `wait(5000, function(){console.log("test!")})`. BTW, it's better to use setTimeout if you need callback –  Nov 26 '17 at 19:14
  • I think use `requestAnimationFrame(function(){})` maybe better than `nothing` in `while` loop, and doing nothing in while loop will cost much cpu. – gogog May 07 '20 at 11:58
1

JavaScript is single-threaded

It is impossible to make a synchronous delay in javascript, simply because JavaScript is a single-threaded language. The browser (most common JS runtime environment) has what's called the event loop. So everything that the browser does happens in this very loop. And when you execute a script in the browser, what happens is:

  1. The event loop calls your script
  2. Executes it line by line
  3. Once the script has finished*, the event loop continues running

Notice that all of this is happening during a single frame of the event loop! And that means that no other operation (like rendering, checking for user input, etc.) can happen before the script has exited. (*) The exception is async JavaScript, like setTimeout/Interval() or requestAnimationFrame() which are not run on the main thread. So from event loops prespective, the script has finished running.

This implies that if there were a synchronous delay in JavaScript, the whole browser would have to wait for the delay to finish, and meanwhile it's unable to do anything. So there is no, and there won't be any synchronous delay in JS.

Alternative - Maybe?

The alternative depends on the actual thing you want to do. In my case, I have a requestAnimationFrame() loop. So all I needed to do was to store the time, and check between the old time and new time in the loop.

let timer =
{
   startTime: 0,
   time: 1000,     // time for the counter in milliseconds
   restart: true   // at the beginning, in order to set startTime
};

loop();
function loop()
{
   if(timer.restart === true)
   {
      timer.startTime = Date.now();
      timer.restart = false;
   }
   
   if((Date.now() - timer.startTime) >= timer.time)
   {
      timer.restart = true;
      console.log('Message is shown every second');
      // here put your logic 
   }

   requestAnimationFrame(loop);
}
no name
  • 13
  • 5
0

Here's how you can use the JQuery doTimeout plugin

jQuery('selector').doTimeout( [ id, ] delay, callback [, arg ... ] );

From the docs: "If the callback returns true, the doTimeout loop will execute again, after the delay, creating a polling loop until the callback returns a non-true value."

var start = Date.now();
console.log("start: ", Date.now() - start);
var i = 0;
$.doTimeout('myLoop', 5000, function() {
  console.log(i+1, Date.now() - start);
  ++i;
  return i == 5 ? false : true;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-dotimeout/1.0/jquery.ba-dotimeout.min.js"></script>
user2314737
  • 27,088
  • 20
  • 102
  • 114
0

Node solution

Use fs.existsSync() to delay

const fs = require('fs');
const uuidv4 = require('uuid/v4');

/**
 * Tie up execution for at-least the given number of millis.  This is not efficient.
 * @param millis Min number of millis to wait
 */
function sleepSync(millis) {
    if (millis <= 0) return;
    const proceedAt = Date.now() + millis;
    while (Date.now() < proceedAt) fs.existsSync(uuidv4());
}

fs.existsSync(uuidv4()) is intended to do a few things:

  1. Occupy the thread by generating a uuid and looking for a non-existent file
  2. New uuid each time defeats the file system cache
  3. Looking for a file is likely an optimised operation that should allow other activity to continue (i.e. not pin the CPU)
Peter L
  • 2,921
  • 1
  • 29
  • 31
  • If you down vote, please explain why. I find this technique to be effective but if it's wrong I really want to know why. I expect fs.existsSync() to not pin the CPU which is beneficial. – Peter L Jun 05 '20 at 17:23
  • You don't explain what `uuid/v4` is used for. As a beginner to Javascript, I'd like to know what each piece of the code does and why it works for you. – Jonathin Jun 16 '20 at 07:36
  • Thanks @jramos775, I see now it wasn't very obvious. – Peter L Jun 17 '20 at 19:09
0

Inspired by @andrew65952 but more modern-like and faster

function wait(ms) {
  const now = Date.now()
  while (Date.now() - now <= ms) { /* do nothing */}
}
vdegenne
  • 12,272
  • 14
  • 80
  • 106
-1

Solution using function generators. To show that it can be done. Not recommended.

function wait(miliseconds){

  const gen = function * (){
     const end = Date.now() + miliseconds;
     while(Date.now() < end){yield};
     return;
  }
  
  const iter = gen();
  while(iter.next().done === false);
}


console.log("done 0");
wait(1000);
console.log("done 1");
wait(2000);
console.log("done 2");
kemicofa ghost
  • 16,349
  • 8
  • 82
  • 131
  • This generator function makes no sense, it could trivially be inlined. This is just the same solution as in the other answers, but unnecessarily complex. – Bergi Jan 22 '19 at 20:25
  • @Bergi Thanks for your opinion. I wouldn't agree with unnecessarily complex though. It's the same argument when async/await is done with function generators; a complex wrapper just to use function generators with an underlying async/await and yet it's widely used. – kemicofa ghost Jan 22 '19 at 20:29
  • 3
    This has nothing to do with async/await or running user-supplied generators in a specific way - it's not even asynchronous. It's a local function inside `wait`, and deserves to be simplified. There is absolutely no advantage in using generators here - the only difference is that it burns the CPU cycles on different instructions. – Bergi Jan 22 '19 at 20:32