如何在Javascript中制作精确的睡眠功能,可能使用承诺?

Posted

技术标签:

【中文标题】如何在Javascript中制作精确的睡眠功能,可能使用承诺?【英文标题】:How to make a precise sleep function in Javascript, possibly using promises? 【发布时间】:2020-08-16 19:31:11 【问题描述】:

我正在尝试在 javascript 中创建睡眠功能。

drawLinesTohtmlCanvas() 函数用于在 HTML 画布上绘制随机线条,用户可以实时看到正在绘制的线条。

对于这个例子,我使用了 500 毫秒的延迟,但希望能够达到 1 毫秒(或者将来甚至小于 1 毫秒的分辨率)

最初我遵循了这篇文章的答案: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);
        
    

而且效果很好(上图)。它很高效,完全没有减慢浏览器的速度,让我可以控制时间。

问题是setTimeout() 似乎无法降低到 1 毫秒的精度,而这是我对这个函数所需要的。

所以我尝试了自己的方法如下:

    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));
        
    

这个很慢,while循环等待合适的时间耗尽了所有浏览器资源,完全无法使用。此外,随着函数 drawLinesToHtmlCanvas() 的运行,线条不会更新到画布元素。

setTimeout() 的 promise 解决方案非常棒,只是不够精确,无法满足我的要求。

我可以做出类似于第一个示例的承诺吗?但不是使用setTimeout(),而是使用与我的Date() now 与 end ms 比较类似的算法,因为这样会更准确吗?

线条现在需要能够被绘制到 1 毫秒并具有实时更新,用户需要能够看到画在画布上的线条。

【问题讨论】:

显示器甚至不会刷新接近那么快的任何地方 解释器永远不允许 setTimeout 在几毫秒之前安排回调(对于某些浏览器,最小值可能高达 12 毫秒),除非您将其传递为零(然后这甚至不是保证)。获得流畅动画的首选方法是使用回调调用requestAnimationFrame setTimeout 甚至故意抖动(或确实)来对抗我认为的幽灵 @CertainPerformance 最终的函数将在屏幕上绘制数百万次,(想想从微小组件中缓慢构建的视觉数据)我考虑过批处理(例如,每毫秒 2000 个绘制事件) 但认为只是为每个绘制事件添加一个微小的延迟会更简单。 @Touffy 好吧,也许我一直走错路,我会看看 requestAnimationFrame 【参考方案1】:

即使setTimeout 确实在如此短的时间范围内工作,这也可能不会成功。当您使用回调和/或承诺时,您依赖于 JS 运行时的事件循环。这个事件循环只会尽可能快地执行你的回调。该架构将施加滞后,当您低于 1 毫秒时,这些滞后将变得可见。 setTimeout 中的回调在经过 N 毫秒后并未完全执行。经过 N ms 后,它才有资格被执行。只有在轮到另一个事件循环滴答时才会最终调用它。

至于您的第二种方法,它并不完全“用完资源”。问题是您不再使用事件循环。但是你必须记住,JS 是单线程的。并且正因为如此,当 JS 代码不停地执行时,它根本不会让用户与 UI 交互。用户只能在事件回调执行之间做一些事情。因此,除非您想破坏用户体验,否则不要在浏览器中的 JS 中使用长时间运行的 while。也许除非您使用 Web Worker,因为它们可以让您创建新线程,但是您将无法从那里绘制任何内容。

一般而言,您对动画的处理方式是“画一些东西然后睡觉”是相当幼稚的。高性能和流畅的动画是视频卡的目的,尽管在浏览器中编写它以有效利用视频卡可能很棘手。如果你想在浏览器中制作动画,那么你必须找到专门为 Canvas 或 WebGL 上的动画制作的特定浏览器函数调用。 也许从这里开始: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations

还要考虑您是否真的需要每秒帧数。超过1000fps?显示器可以吗?性能影响如何?

【讨论】:

以上是关于如何在Javascript中制作精确的睡眠功能,可能使用承诺?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TypeScript 中实现睡眠功能?

循环中 Promise 的 javascript 睡眠功能

javascript / node.js中的睡眠功能[重复]

JavaScript 中是不是有睡眠/暂停/等待功能? [复制]

是否有等效的 Javascript 或 Jquery 睡眠功能?

为啥C没有像javascript中的setTimeout这样的非阻塞睡眠功能