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.