Have you ever fiddled with your browser's DevTools before, trying to implement some code that involves sleeping but discovered to your dismay that there is no native sleep function in Javascript? Or perhaps you tried to implement that on JS served from a website? I've been there before, so let me describe the workarounds I found for this, and why they're all unsuitable if your code involves making network requests (and what other options you have for that).
The fallacy of sleeping
ECMAScript does not define a sleep function. As a result of that, there is a long list of assorted hacks to attempt to make one that works out. All of them have shortcomings as you'll see in the next paragraph.
The official Stack Overflow question for sleeping in Javascript, What is the JavaScript version of sleep()? has 2408 upvotes (and 12 more since drafting this article) and was viewed 2.8 million times over a period of more than 11 years, proving how essential sleeping is in some applications, as well as the frustration of other web developers who now have to make their own sleep function.
Is there a better way to engineer a sleep
in JavaScript than the following pausecomp
function (taken from here)?
function pausecomp(millis)
{
var date = new Date();
var curDate = null;
do { curDate = new Date(); }
while(curDate-date < millis);
}
This is not a duplicate of…
Now, sleeping is very important sometimes. Suppose you are fetching data from an API, and that data is spread over several "next pointers" requiring you to make several requests. If you fire all the requests at once, or simply have a malfunctioning sleep
, you risk getting blocked with 429 Too many requests
status codes. Instagram's GraphQL endpoint is an example of an API that does this. There were several times I made my code pass through different implementations of sleep functions between requests, only to find out on execution that all of the requests fired at once causing most to fail with 429 status codes!
Any solution that uses busy waiting is unsuitable for in-browser use because when it runs, the browser will display a banner warning that a script is making the browser slow and will give the user the option to stop it (at least Firefox does it, Chrome might also do this too). This will stop your javascript code that's busy waiting and might even break your page. So we must explore other options.
The top solution uses the following snippet:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// It's called using `await sleep(ms)`.
The problem
This works magnificently, but only if your async functions aren't called by normal functions. It use await so it must be called inside an async function. This has the unpleasant consequence of resuming execution of non-async callers while the function is sleeping. If you assumed your code will run serially and that callers won't resume execution until the function it calls finished, then async
breaks that assumption.
You see, in Javascript, there are async
functions which run independently of the parent function. To force the parent function to wait for the async function using the await
keyword, the parent function must also be labeled async, and this starts a chain reaction where every function from the bottom to top becomes an async
function. Though there is nothing wrong with writing your code like that, do note that every await
keyword returns a promise, even if the async function returns some other type (It's wrapped as an argument in the promise resolve()
). So now you must put your remaining function body around the then()
call, and whenever you return things, those are wrapped in other threads, so immediately after the then call, you need to put .then()
again if you want to make use of the return value, and if it's the last then()
in the function, the return value inside its Promise propagates up to the async
caller.
Think of it like this:
async function someFunc() { /* ... */ }
async function callee() {
p = await someFunc()
// `p` is a Promise
p.then(ret => {
// `ret` is whatever `someFunc` was supposed to return
})
}
And there are no good solutions to this problem as you'll see below. The other answers to the Stack Overflow question are overtly complicated.
Recurse, don't loop?
I have found out the hard way that merely sleeping inside loops, using the top solution in the Stack Overflow question, leave you an unpleasant surprise at runtime - All the sleep
s run simultaneously for each value of the loop counter (if you used a while statement then it's infinity) and then as many loop bodies fire at once. This eventually crashes the browser.
Recursion is a workaround where you call the asynchronous function inside itself with the same parameters, immediately returning its value in the same statement. This also allows you to implement conditions to continue recursing, or quasi-looping, in. This is how it would work.
async function sleep(ms) { /* exact same `sleep` function as above */ }
async function foo(bar) {
someAsyncFunc().then(retSomeAsyncFunc => {
if (/* some condition involving `retSomeAsyncFunc` matches */) {
// This returns a promise with the return value inside
// to whoever called `foo()` initially
return foo(bar)
}
// Additional processing...
return someValue // Optional, can return nothing at all
})
}
The big problem with using recursion in this context is that you might hit a call stack size limit if you recurse ("loop") too much. Each browser has a different call stack limit so be careful while recursing.
This approach also becomes very messy as soon as you need nested loops. How will we make the outer loop into a recursion that calls a recursion of the inner loop, that itself does arbitrary things if there is only one defined function to recurse with?
Even if you didn't understand the previous paragraph, it's easy to see that two loops cannot share one recursing function. See the problem?
setInterval with a global variable?
Another workaround is to use setInterval()
with the amount of time you want to sleep, and your loop body in the callback function. This approach has two disadvantages:
- The callback can't take arguments so your function arguments must be put inside global variables, which may or may not be suitable for your program.
- This approach quickly disintegrates as you add more nested loops inside it.
Atomics.wait?
I had the most luck getting this to work properly. It works outside async functions as well. It seems to work in NodeJS but not in Firefox and Chrome.* The rest of the browsers don't implement Atomics.wait
. So this solution won't work for Devtools scripts or client-side JS that your webpage fetches.
function sleep(n) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
}
*It's because they are running in the main thread. There is a whole explanation of special cases you can call Atomics.wait in specific worker threads, such as inside WebGL renders, but it's off-topic so I won't cover it here.
Use NodeJS?
There is a true sleep function available as a third-party npm module called sleep
, and you can count on it not splitting a pathway of code into two (and 3, and 4 each time you call it, potentially meaning that many network requests). It waits until the timeout elapses and runs the next statement below it. There is no need to tinker with promises or async/await to make it work.
This in my opinion, is the ultimate sleep function; it's written in a language that has native support for that feature, C++. Unfortunately there is no such equivalent inside browsers. This is specifically a NodeJS solution.
A disadvantage of using this module is that the entire event loop halts while sleeping. This may be what you want, but if you were looking for a little of both worlds, that is, sleeping for a single thread, it's not here.
Also, since this module is written in C++, it needs to be compiled to install it. You can't do this without installing the Visual Studio build tools and toolkits alongside Node, which you may not have space for.
Javascript is not Java
Javascript has a completely different execution flow from traditional languages. You must write your code to fully use one paradigm, async/promises, or the other, loops. There is no middle course as this article just demonstrated. And the number of built-in functions that return promises makes using loops for the blocking operations more and more infeasible.
Do not write your Javascript apps in the traditional way if you are going to make network requests. All of them are asynchronous and this forces you to make all parent functions asynchronous as well, and chain your function bodies with then
. You may use different implementations of sleep, but only if you do not need loops. Unroll your loops if possible so that they don't use loop statements.
And we're done
I'm open to article corrections and better suggestions for handling this problem if you have any, as well as feedback you might have on this issue.
Top comments (0)