DEV Community

loading...
Cover image for Wait for it: Implementing a sleep() function in JS

Wait for it: Implementing a sleep() function in JS

Daniel Sasse
Fullstack Dev w/ experience in JS/React and Rails
・3 min read

In my last blog entry, I had created a visualizer to model an approach to solving a Leetcode algorithm problem. I had planned to create a second entry in that series this week, however I ran into an obstacle:

How do I slow down the operation speed of a function to the point where I could create visual models and allow the viewer time to process them before it updated to the next step?

Most program languages have a sleep function/method that can be invoked to delay the next operation in a function. For example, Ruby has sleep(1000) and Python has time.sleep(1) to "pause" operation for 1 second, but there is no direct correlate in Javascript.

setTimeout and setInterval

Asynchronous actions in Javascript can usually call upon one of these two functions. setTimeout allows us to wait a specified amount of time before invoking a new callback function with setInterval operating similarly except the delay will reset and continue repeating.

For the purposes of slowing down operation for something like a while loop, neither of these are directly helpful since they delay the invocation of a new action rather than the delay the original function in which they were called.

Incorporating Promises

In Javascript, a Promise object represents the eventual completion of an action. For example, if you have ever worked with an API call and made a request to the server, that request returns a "pending" Promise that will ultimately become "resolved" or "rejected". When creating a new Promise object, we can pass in two callback functions as arguments. The first argument is the function that will be invoked when the Promise is resolved, and the second (optional) is invoked if the Promise is rejected.

Example:

const sleep = (ms) => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};
Enter fullscreen mode Exit fullscreen mode

In the example above, the function accepts a single parameter which will reflect the time in milliseconds that we would like the function to sleep for. We then create a new Promise object and use setTimeout in the callback function for the resolution of the Promise. We do not need a callback for the Promise reject here, as this will never arise in this use case.

setTimeout itself takes in two arguments: a callback function and a duration in milliseconds in which to delay invoking the callback. For the delay, we simply pass the ms parameter of the sleep function. For the callback, we will use the resolve action of the Promise. This means that the state of the Promise will not be set to resolved until that delay time has passed.

Usage

With the async/await keywords in our functions, we can tell a function to "await" the resolution of a Promise before continuing its next action. If we combine this with a setTimeout action, we can effectively create a sleep() function in Javascript.

Example:

const sleep = (ms) => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};

const action = async () => {
    for (let i = 1; i < 5; i++){
        console.log(`Round ${i}`)
        console.log('Waiting for 500ms')
        await sleep(500)
        console.log('Posting')
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the final operation of each iteration will not run until the promise from the sleep() function has been resolved.

Sleep Example

Initially the uses for this action compared a "regular" timeout or interval may seem limited. However, this allowed me to solve my initial challenge of trying to visualize the progress of an algorithm solution to create a visualizer. Normally, the speed at which a function runs would make it impossible for a viewer to see the incremental changes in values or current positions in a traversal. However, if we render a component, and provide a brief sleep in each iteration it allows the user to view the changes occurring at each step.

For example, we can visualize a depth first search process through a grid to map out the "land area" of islands in an "ocean":

Island Example

Hopefully this implementation of a sleep function opens up new options for you in your coding. More information on the mapping algorithm above and the visualizer itself will come next week!

Discussion (14)

Collapse
vittorivabella profile image
Vitto Rivabella

Interesting post! Just wrote some multithreading stuff on Python and is definitely similar to Js :)

Collapse
martixy profile image
Martin Ninov

I can't reply to your question about the GIL below. (?)
JS does not have a GIL. It does not need a GIL. It is single-threaded in general. Though there do exist things like workers which allow limited, but true multi-threading capabilties.

Collapse
dsasse07 profile image
Daniel Sasse Author

Thank you Martin!

Collapse
dsasse07 profile image
Daniel Sasse Author

Your multithreading article was really interesting. Definitely some similarities with threading.join(). Thank you for reading!

Collapse
vittorivabella profile image
Vitto Rivabella

Yeah, one quick question, does Js implements something like the Python GIL?

Thread Thread
dsasse07 profile image
Daniel Sasse Author • Edited

From my understanding of GIL from your post, there is something similar. JS uses a single threaded call stack to resolve functions so the most recently invoked function must be resolved before the previous one. Function execution proceeds line by line. When utilizing setTimeout and setInterval like I described above, it actually adds the callback function passed to those to a separate call stack that has priority at during run time. When the timer is up, these functions execute before returning to the main call stack.

This is why I couldn't just use a timeout by itself, because the main thread would proceed and finish execution without waiting for the timeout stack to execute and return its value. Hope that helps!

Thread Thread
tuanfrontend profile image
Eden Tuấn

Hello

Collapse
tuanfrontend profile image
Eden Tuấn

Hello

Thread Thread
tuanfrontend profile image
Eden Tuấn

Hello

Thread Thread
tuanfrontend profile image
Eden Tuấn

Hello

Collapse
peerreynders profile image
peerreynders • Edited

This is why I couldn't just use a timeout by itself, because the main thread would proceed and finish execution without waiting for the timeout stack to execute and return its value.

This statement confuses me.

The main thread runs the event loop and typically in the browser the event loop doesn't exit and in node JS the event loop doesn't exit when there are still pending asynchronous tasks.

Given your sample code when you run action() a Promise value is immediately returned before any rounds are posted. So strictly speaking from your point of view the "main thread has finished" (though of course it hasn't because the event loop continues to process the scheduled code).
Now granted the returned promise doesn't resolve until all the rounds are posted - but that would imply that your event loop only exits once all promises were settled.

That would mean that you only need a single promise that doesn't resolve until all the rounds are posted.

So while it is certainly educational to implement something like sleep(), I would think twice before using it primarily because sleep() is a concept from synchronous code bases.

Typically in an asynchronous environment "scheduling tasks" is a more appropriate mental model, e.g.:

function roundWork(i, delay) {
  console.log(`Round ${i + 1}`);
  console.log(`Waiting for ${delay}ms`);
}

function postWork() {
  console.log('Posting');
}

function launchAction(delay, rounds, done) {
  // start the round
  roundTask(0);

  // function declarations hoist to the top
  function roundTask(i) {
    if (i < rounds) {
      roundWork(i, delay);
      setTimeout(postTask, delay, i);
    } else {
      setTimeout(done);
    }
  }

  function postTask(i) {
    postWork();
    roundTask(i + 1);
  }
}

new Promise((done, _reject) => {
  launchAction(500, 4, done);
}).then(() => {
  console.log('complete');
});
Enter fullscreen mode Exit fullscreen mode

In order of increasing priority:

For details on the event loop see In The Loop - JSConf.Asia and the blog post Tasks, microtasks, queues and schedule.

Specifications for future scheduling API's are being actively developed.

The point being - the sooner one adopts organizing code by tasks that can be scheduled in an asynchronous environment like JavaScript the better. Sticking to a more "synchronous" style of predominantly controlling the (main thread) flow is ultimately limiting (even though it initially may seem more familiar).

Collapse
dsasse07 profile image
Daniel Sasse Author

Hi peerreynders, thank you for your insight and for the resources you shared. I will definitely check them out to see how they can be applied. For the purpose of the visualizer that inspired this post, the sleep action is allowing me the opportunity to create the visual model for the data structure before mutating the structure in the next iteration.

Collapse
benjaminwfox profile image
Ben Fox

Cool use-case, nice explanation!

Collapse
imagineeeinc profile image
Imagineee • Edited

thanks, really needed this, and I learnt a bit on promises