1

I am trying to make a sleep function in Javascript.

The function drawLinesToHtmlCanvas() is meant to draw random lines to a HTML canvas and the user is meant to be able to see the lines being drawn in real time.

For this example I use a delay of 500ms, but would like to be able to go to 1ms (or even less than 1ms resolution in the future)

Originally I followed a answer from this post: What is the JavaScript version of sleep()?

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

    async function drawLinesToHtmlCanvas() {

        // Get canvas and context here...

        var drawSpeed = 500; // ms.

        for (i=0; i<lines; i++) {

            // Draw lines to canvas...

            await sleep(drawSpeed);
        }
    }

And that worked very well (above). It was efficient, did not slow the browser down at all and let me have some control over the timing.

The issue was that setTimeout() cannot seem to go down to 1ms precision and this is something I require for this function.

So instead I tried my own approach as follows:

    function sleep(ms) {
        ms = parseInt(ms);
        var now = new Date();
        nowMs = now.valueOf();

        var endMs = nowMs + ms;

        while (endMs > nowMs) {
            nowMs = new Date().valueOf();
        }

        return true;
    }

    function drawLinesToHtmlCanvas() {

        // Get canvas and context here...

        var drawSpeed = 500; // ms.

        for (i=0; i<lines; i++) {

            // Draw lines to canvas...

            while (!sleep(drawSpeed));
        }
    }

This one is very slow, the while loop waiting for the right time uses up all the browsers resources, it's completely unusable. In addition as the function drawLinesToHtmlCanvas() is running, the lines are not being updated to the canvas element.

The promise solution with the setTimeout() was fantastic, it is just not precise enough for my requirements.

Can I make a promise that works similar to the first example? But instead of using setTimeout() it uses a similar algorithm to my Date() now vs end ms comparison, as that would be much more accurate?

The lines need to be able to be drawn down to 1 ms for now and have real time updates, the user needs to be able to see the lines being drawn to the canvas.

Joseph
  • 3,749
  • 9
  • 28
  • 46
  • 4
    Monitors don't even refresh anywhere close to that fast – CertainPerformance May 02 '20 at 09:38
  • 1
    interpreters never allow `setTimeout` to schedule callbacks sooner than a few ms (the minimum can be as much as 12-ish milliseconds for some browsers) unless you pass it exactly zero (and then that's not even guaranteed). The preferred way to get fluid animations is to call [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame) with a callback. – Touffy May 02 '20 at 09:50
  • setTimeout's even have deliberate jitter (or did) to combat spectre I think it was – Jaromanda X May 02 '20 at 10:02
  • @CertainPerformance the eventual function will be drawing millions of times to the screen, (Think of a slow build up of visual data from tiny components) I thought about batches (for example, 2000 draw events per ms) but thought just adding a tiny delay to each draw event would be simpler. – Joseph May 02 '20 at 10:10
  • @Touffy ah ok, perhaps I have been going about this the wrong way, I will look into requestAnimationFrame – Joseph May 02 '20 at 10:12

1 Answers1

1

Even if setTimeout did work on such extremely small time frames this would probably not have worked out. When you use callbacks and/or promises you rely of JS runtime's event loop. This event loop only executes your callback as fast as it can. The architecture is going to impose lags that will become visible when you go below 1ms. The callback in setTimeout is not exactly executed after N ms passes. After N ms passes it only becomes eligible to be executed. And it gets invoked finally only when its turn comes on another event loop tick.

As for your second approach it does not exactly "use up resources". The thing is you no longer use event loop. But you must remember that JS is single-threaded. And because of it when JS-code executes non-stop it will not let user interact with UI at all. User can do something only between event callback executions. So don't ever use long running whiles in JS in browser unless you want to ruin user experience. Maybe unless you use Web workers because they will let you create new threads, but then you wouldn't be able to draw anything from there.

In general your approach to animation as "drawing something and then sleeping" is rather naive. Performant and smooth animations are what the video cards are made for although writing it in browser to efficiently utilize video card may be tricky. If you want to make animation in browser then you have to find specific browser function calls made specifically for animation on a Canvas or WebGL. Maybe start here: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations

Also think about if you actually need so may frames per second. Over 1000fps? Can the monitor make it? What about performance impact?

Gherman
  • 4,957
  • 6
  • 36
  • 58