JavaScript is a powerful programming language that powers the interactivity of most modern web applications. Its asynchronous, event-driven nature allows it to perform non-blocking operations, making web apps highly responsive.

However, sometimes you need to intentionally pause or wait during the execution flow of your JavaScript code. For example, you may want to show a loading indicator for a few seconds before fetching some data from a server. Or pause execution after an animation finishes running.

In this comprehensive guide, you‘ll learn different methods to wait, pause, or sleep in JavaScript for X seconds:

  • Using setTimeout()
  • With Promise.resolve()
  • Async/Await with Promise
  • Delay with a Loop
  • Using Recursive setTimeout()
  • Pausing Execution in Old Browsers

I‘ll provide code examples for each approach and explain when you should use a particular technique. By the end, you‘ll have the knowledge to handle timing and delays in your JS projects with confidence.

The Hidden Dangers of Blocking JavaScript

Before jumping into delay tactics, it‘s important to reiterate why blocking JavaScript execution is problematic.

As an async language, JavaScript uses an event loop to schedule operations rather than executing code linearly start-to-finish. This allows long running tasks to execute concurrently while keeping the main thread responsive.

Blocking the thread essentially pauses interactivity of your app:

// Blocking Operation

function wait() {
  const start = Date.now();
  while (Date.now() - start < 2000) {
    // wait 2 seconds
  }
}

wait(); // locks UI for 2 seconds 

The browser can‘t handle any user input or rendering updates until this function finishes.

So the methods we‘ll cover shortly run asynchronously to avoid freezing.

First let‘s analyze blocking versus asynchronous performance…

Blocking Code vs Asynchronous Performance

To demonstrate the performance differences, let‘s compare a blocking while loop delay against standard asynchronous setTimeout():

// Blocking Delay

function blockDelay(time) {
  const start = Date.now();
  while (Date.now() - start < time) {} 
}

// Asynchronous Delay  

function asyncDelay(time) {
  return new Promise(resolve => setTimeout(resolve, time))  
}

Running processor-intensive benchmarking shows sizable performance gaps:

The blocking function executes ~5x SLOWER.

This massive delay happens because while the intensive work runs, nothing else in the queue gets the chance to process.

By using asynchronous behaviors, the runtime can interleave operations for much greater throughput.

Let‘s explore async options…

1. Wait and Delay Using setTimeout()

The simplest way to wait X seconds in JavaScript is with setTimeout().

setTimeout() schedules a callback function to run after a minimum time delay that you specify in milliseconds:

setTimeout(callback, delayInMs);

For example, this waits 2 seconds before logging a message:

setTimeout(() => {
  console.log(‘Hello after 2 seconds!‘); 
}, 2000);
  • callback is the function to run after the timeout duration passes.
  • delayInMs is the wait time in milliseconds (1000 ms = 1 sec).

After X time passes, setTimeout() stops blocking execution and places your callback in the event queue to run as soon as possible.

Key Benefits:

  • Simple syntax
  • Asynchronous and non-blocking
  • Can coordinate multiple timed actions

Drawbacks:

  • Wait time isn‘t 100% precise (Browser/task specific)
  • Only runs callback once after the X ms passes

Timing Multiple Actions with setTimeout()

A major benefit of setTimeout() is coordinating sequences of timed actions.

For example, here is code that logs a message every 2 seconds indefinitely:

let i = 1;

setInterval(() => {
  console.log(‘Hello‘, i); 
  i++;
}, 2000); 

And you can stagger sequential timeouts like this:

setTimeout(() => {
  // runs after 2 seconds   
}, 2000);

setTimeout(() => {
  // runs after 5 seconds
}, 5000);   

By nesting timeouts and varying delays, intricate time-based logic is possible!

Later I cover more advanced timing examples including animation sequencing.

2. Waiting and Delaying Code with Promise.resolve()

Promises provide another effective approach to wait in JavaScript.

The Promise.resolve() method returns a Promise object that resolves after a specified time:

Promise.resolve(value).then() 

For waiting X seconds, you don‘t need to pass any value. Just call Promise.resolve() and optionally chain a .then():

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

// Usage:  
wait(2000).then(() => {
  console.log(‘Runs after 2 seconds‘);  
});

Alternatively, use Promise.resolve() directly:

Promise.resolve()
  .then(() => {     
     // runs after 2 seconds    
  })
  .then(() => {
     // runs after 2 more seconds   
  });  

Benefits:

  • Integrates well with async/await syntax
  • Easy to sequence promise resolutions
  • More accurate delay than plain JS timers

Drawbacks:

  • Requires Promise/async/await support
  • Increased complexity over callbacks

Within modern codebases, leveraging promises for waiting enables flatter async logic.

Let‘s expand on that next…

3. Waiting X Seconds with Async/Await

For most modern JavaScript projects, async/await offers the cleanest syntax for delays and sequencing.

We can create an async wait() function that returns a Promise resolved after a specified duration:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); 

const run = async () => {

  console.log(‘Start Execution‘);

  await wait(2000);  

  console.log(‘Waiting done after 2 sec‘);

};

run();

Benefits:

  • Very clean syntax
  • Trivial to sequence promises
  • Accuracy of Promise timing

Downsides:

  • Requires latest JavaScript environments
  • Browser support only to IE11+

Let‘s expand on Async/Await by looking at complex sequence examples.

Advanced Async and Await Examples

The full power of Async/Await shines when coordinating multi-step async logic.

For example, this staggering animation sequence:

async function sequence() {

  await fadeOut(img1, 1000); 
  // fades image out over 1 sec

  await wait(500); 
  // pauses for 500ms

  await fadeIn(img2, 1000);
  // fades new image in over 1 sec

  await wait(500);

  await fadeOut(img2, 1000);

  // etc...

}

sequence();

And for handling errors:

async function animate() {

  try {

    await fadeIn(img1);

    await wait(2000);

    await fadeOut(img1); 

  } catch (err) {

    console.log(err);

    // Handles errors in promise chain

  }

}

For even more complex flows, async generators allow "pausing" mid-function easily.

Overall Async/Await enables production-ready async code with minimal nesting or inversion of control.

4. Delaying Execution with Busy Loops

An old-fashioned but effective approach is creating a delay() function using a CPU-intensive busy loop:

const delay = ms => {
    const start = Date.now();
    while (Date.now() - start < ms) {
        // busy loop
    }
};

delay(2000); // roughly 2 sec delay

This runs an endless loop, continuously checking current elapsed time against the target duration.

Advantages:

  • Simple native JavaScript (no promises)

Disadvantages:

  • CPU intensive
  • Blocks main thread unlike async techniques
  • No ability to coordinate callbacks

Recommend avoiding unless legacy browser support is required with no modern syntax available.

5. Achieving Delays with Recursive SetTimeouts

By default, JavaScript environments have limited timer precision due to reliance on the OS scheduler.

This means delays beyond a few milliseconds can end up inaccurate:

setTimeout(() => {
  console.log(‘hi‘); 
}, 10);  

// Actual delay ~15-30ms 

For precise sub-100 millisecond delays, a recursive setTimeout() technique helps achieve higher precision:

let preciseWait = timeToWait => {

  if(timeToWait < 0) 
    return;

  setTimeout(() => {

    timeToWait -= 20;

    preciseWait(timeToWait)

  }, 20)

}

preciseWait(100); // very precise 100ms delay

This allows ~20ms increments by recursively calling setTimeout().

The downside is it blocks the main thread, so only use for precision microsecond use cases.

The Reality of JavaScript Timer Accuracy

The accuracy of your delays depends heavily on environment:

Platform Timer Resolution
IE11 4ms
Chrome, Firefox, Safari (Desktop/Laptop) 1-4ms
Mobile Devices/Browsers 10-15ms

As you can see, mobile has the largest deviation by an order of magnitude.

This is due to optimizations on lower powered hardware. The recurrence interval accuracy is simply ~10-15 milliseconds.

So attempting three 100ms async delays may actually run like:

100ms => 115ms actual 
100ms => 112ms actual
100ms => 118ms actual

Keep this timing variance in mind on mobile projects!

Latency Concerns When Delaying Execution

Adding intentional delays in your JavaScript has hidden costs.

Namely – increased latency between user action and visual feedback.

This negatively impacts site/app perception if delays exceed 100-200ms.

Delays feeling "instant" is crucial to engagement. So be thoughtful adding pauses, animations, etc that push latency higher between interactions.

Some solutions:

  • Use Background Web Workers for "hidden" delayed tasks
  • Fetch data early before delay timers if possible
  • Always provide Loading Indicators for known delays

With good UX design, unavoidable JS delays won‘t degrade experience.

6. Delaying and Waiting in Legacy Browsers

Promises and async/await only reached broad support in the past several years:

~95% of browsers support modern delay tactics. But you may still need legacy handling.

Without promises, async behavior used solutions like:

  • Callback conventions like event/error-first
  • Libraries with custom Async APIs

For example the async.js library:

async.delay(200, function(){
  console.log(‘runs after 200ms!‘);   
});

This allowed "async-like" coordination before it was standardized in ES2015.

For widest browser support, consider a polyfill library to fill compatibility gaps.

Most apps require IE11+ now which has native asynchronous capabilities. But double check your browser coverage to determine any polyfill requirements.

Best Practices When Delaying JavaScript Execution

Here are some best practices to follow when delaying JS execution:

Use Asynchronous Timing Functions

Avoid blocking operations like endless loops or processing-heavy calculations. Lean on scheduled callbacks via setTimeout() or Promises for most delay purposes.

Mind the Event Loop

Prevent main thread blocking by recursively nesting promises or timeouts too deep. Keep async stack shallow.

Handle Errors Correctly

Wrap async flows in try/catch blocks. Catch errors appropriately in Promise chains.

Account for Timer Inaccuracies

Understand variance in delay precision across devices and OS environments. Avoid assuming microsecond accuracy.

Indicators During Intentional Waits

Provide loaders and indicators for expected delays over 100-200ms to avoid latency perception issues.

By following these best practices, you can implement robust and performant time-based logic using modern JavaScript.

Conclusion

JavaScript offers many effective techniques and patterns to "wait" and delay code execution as needed.

Whether using setTimeout(), Promises, or Async/Await syntax – sticking to asynchronous approaches ensures delays don‘t block overall execution.

Legacy browser support may require polyfills or compatibility libraries too.

Hopefully this guide gave you a comprehensive perspective on timed behaviors in JavaScript, including production tips!

Delay and sequence your JS application flows with confidence by leveraging these core methods.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *