JavaScript 睡眠函数

17

是的,我知道 - 那个问题有成千上万个答案。请不要告诉我关于 setTimeout 方法因为 - 是的,用那个方法一切都可能,但不像使用 sleep() 方法那么容易。

例如:

function fibonacci(n) {
    console.log("Computing Fibonacci for " + n + "...");
    var result = 0;

    //wait 1 second before computing for lower n
    sleep(1000);
    result = (n <= 1) ? 1 : (fibonacci(n - 1) + fibonacci(n - 2));

    //wait 1 second before announcing the result
    sleep(1000);
    console.log("F(" + n + ") = " + result);

    return result;
}

如果您知道如何使用setTimeout获得相同的结果,请告诉我;) 菲波那切数列是一项相当简单的任务,因为递归不超过2个,但是n次递归怎么办(例如fib(1)+fib(2)+..+fib(n)),并且每次"+"后休眠?嗯,休眠会更容易。

但是我仍然无法找到实现它的工作示例。while (curr - start < time) { curr = (...) }比较棘手,但它不起作用(只会停止浏览器,然后一次性抛出所有控制台日志)。


3
你考虑过使用更好的算法吗? - Yacoby
1
@Yacoby - 用于计算斐波那契数列?这只是一个例子,我知道它非常慢。 @Oli - 如何将其重写为setTimeout或如何实现有效的sleep()函数。 - Diazath
你需要更好地解释你的问题 - 我知道序列是什么,但我不知道你想要做什么。 - jcuenod
2
这个问题怎么会得到任何赞成票? - Josh Smeaton
每次打印后是否有清空控制台的方法?可能只是缓存了输出。 - Lawrence Dol
5个回答

51

这个问题是在询问如何在JavaScript中实现sleep(),对吗?

function sleep(ms) {
  var start = new Date().getTime(), expire = start + ms;
  while (new Date().getTime() < expire) { }
  return;
}

我刚刚这样测试了一下:

console.log('hello');
sleep(5000);
console.log('world');

对我来说可行。

(作为元评论:我来到这里是因为我有一个特定的需要,需要这个函数。当你需要在等待值时需要阻塞时,就会遇到这样的需要。即使在JavaScript中也是如此。)


1
不是很清楚。可能问题涉及同步与异步编程以及如何异步编码。根据问题的提问者在被接受的答案上的评论,这一点变得明显。回答已经被接受的问题可能会引起争议。但是,这当然是具体情况具体分析,可能会有好处。提问者可能会在以后更改已接受的答案。 - cfi
1
编写一个同步的 sleep() 函数,只是偶尔烧掉 CPU 周期并没有太多意义。在大多数操作系统中,即使你这样做,也无法防止控制权被从你的进程中取走。实际上,正是因为这个原因,主动循环和轮询时间比释放控制权给系统,并要求它在某个时间唤醒你(=事件)更不准确。也许在实时操作系统或非可抢占特权级别上有用例... - cfi
2
另一方面,同步的 sleep() 可以非常有用,例如在可能(但尚未出现)存在非常长时间运行的 JS 任务的情况下测试某些奇特的 setInterval()/clearInterval() 行为。这是唯一回答“如何在 JS 中使用 sleep()”问题的答案。并不是我认为这是大多数人想要的答案,而是因为大多数要求 sleep() 的人实际上并不需要 sleep() - user508633
我知道这种做法不好。但有时候你只需要它。感谢@Murph为我们做出了艰苦的工作。 - Pubudu Dodangoda

20

我并不完全理解你的问题,但我来回答这部分:

如果您知道如何使用 setTimeout 获得相同的结果-请告诉我

根本区别在于许多其他语言中使用的 sleep 是同步的,而 setTimeout(以及许多其他 JavaScript 概念,如 AJAX)是异步的。因此,为了重写您的函数,我们必须考虑到这一点。主要是,我们必须使用回调函数来获取“返回值”,而不是实际的返回语句,因此它将被用于以下方式:

fibonacci(7, function(result) {
  // use the result here..
});

因此,就实现而言:

function fibonacci(n, callback) {
  console.log("Computing Fibonacci for " + n + "...");
  var result = 0;

  var announceAndReturn = function() {
    setTimeout(function() {
      // wait 1 second before announcing the result
      console.log("F(" + n + ") = " + result);
      callback(result); // "returns" the value
    }, 1000);
  };

  // wait 1 second before computing lower n
  setTimeout(function() {
    if (n <= 1) {
      result = 1;
      announceAndReturn();
    }
    else {
      var resultsLeft = 2;

      var handler = function(returned) {
        result += returned;
        resultsLeft--;
        if (resultLeft == 0)
          announceAndReturn();
      }

      fibonacci(n-1, handler);
      fibonacci(n-2, handler);
    }
  }, 1000);
}

我还想指出,不是比使用sleep更容易的解决方案。为什么?因为这段代码是异步的,对于大多数人来说,这比同步代码更加复杂。需要练习才能开始以那种方式思考。

好处在哪里呢?它允许你编写非阻塞算法,这些算法优于它们的同步对应物。如果你之前没听说过Node.js,你可以看一下了解更多相关的好处。(许多其他语言也有用于处理异步IO的库,但只要我们谈论JavaScript...)


1
所以我看来,我别无选择,只能开始那样思考。谢谢。 - Diazath
1
@Diazath:是的,基本上就是这样。不过你不会后悔的,它会释放你的思维 ;) - Jakob
Jakob的回答很扎实,但理解异步编程(回调、上下文/闭包)花费了我比预期更长的时间,不过在多年的C/C++编码后,我正在逐渐掌握。 - Mark Essel

3
在浏览器(或任何其他GUI环境)中,使用类似于sleep()函数的操作存在问题,因为它是一个事件驱动环境,不能以您所描述的方式进行sleep()setTimeout()方法的工作原理是创建一个事件,并将触发时间设置为特定的时间点。因此,系统可以将等待控制权交给事件处理程序,而JavaScript可以自由地继续执行其他任务。
在Web浏览器中,几乎所有操作都是这样完成的。例如,鼠标单击/悬停等功能是事件触发器。Ajax请求不会等待服务器响应;它们设置了一个事件,以便在接收到响应时触发该事件。
基于时间的操作也是使用事件触发器完成的,使用setTimeout()等函数。
事实上,这就是做法。实际上,这是几乎所有良好编写的GUI应用程序的做法,因为所有GUI界面必须能够几乎立即响应诸如鼠标单击之类的事件。
JavaScript sleep()函数(特别是在另一个答案中实现的方式!)会消耗CPU周期,同时等待时钟。 sleep()将保持活动状态,这意味着可能无法立即处理其他事件-这意味着您的浏览器在等待期间似乎会停止响应鼠标单击等操作。这是不好的。 setTimeout()才是正确的方法。总有一种方法可以完成它;生成的代码可能并不像您的示例代码那样简洁和线性,但是事件驱动的代码很少是线性的-它不能是线性的。解决方案是将该过程分解成小的函数。您甚至可以将后续函数嵌入到setTimeout()调用中,这可能有助于使代码看起来至少具有某些线性外观。
希望这能为您解释清楚一些事情。

2
你所说的一切都适用于异步环境下的Javascript。JavaScript作为一种编程语言并没有坚持异步运行时行为。事实上,大多数服务器端JavaScript环境都是同步的;Rhino就是一个很好的例子。 - Pointy
@Pointy - 谢谢;我已经修改了答案,强调我们特别讨论的是浏览器环境。 - Spudley
1
@Pointy 还有那个"非常棒"的 Node.js,它是完全异步的(是的,有一些特殊的同步方法用于读取文件,但那些只是为了配置目的)。但是没错,在一般情况下,JS 并没有什么特别之处,事实上,Node 自己实现了 setTimeout 等等方法 :) - Ivo Wetzel

1

只需使用更好的算法,避免使用循环或递归,并避免需要setTimeout() / sleep()

function fibonacci(n) {
  return Math.round(Math.pow((Math.sqrt(5) + 1) / 2, Math.abs(n)) / Math.sqrt(5)) * (n < 0 && n % 2 ? -1 : 1);
}

使用示例:

// Log the first 10 Fibonacci numbers (F0 to F9) to the console
for (var i = 0; i < 10; i++) {
  console.log(fibonacci(i));
}

1
他并不是在要求更好的算法来计算实际序列。由于某些原因,他想要睡觉(也许是为了证明他的算法对于非微小的N来说非常慢)。虽然问题表达得非常差,但我不会给他投反对票。 - Jakob
@Jakob:我意识到了。实际上,我之前发布了一个不同的答案,解释说实现sleep()是一个坏主意,但它确实可以做到,并且因为这是个坏主意而被淹没在负评中。哦,好吧,现在已经删除了。 - Mathias Bynens
1
哈哈,我明白了。我想这就是给猪拿珠子吧 :) (没有冒犯那些不习惯异步代码的人,但你们错过了一些东西) - Jakob

-1

为什么在计算时你想要“睡觉”?在任何语言中,睡眠几乎总是一个坏主意。它基本上告诉线程在那段时间内停止做任何事情。

因此,在像JavaScript这样只有一个线程(忘记“Web Workers”)的语言中,暂停所有计算会带来什么好处?这是个坏主意,忘了它吧。

现在来看看你写的问题,虽然我不认为这是你实际的问题。为什么在计算这个序列时你想要暂停一秒钟?即使计算序列中的前6个数字,也需要大约8秒钟左右。为什么?有什么可能的理由在递归调用之间暂停一秒钟?停止它。删除它。

如果你只想在完成后一秒钟产生最终结果,那么使用setTimeout和一个使用答案的函数。

function fib(n) {
    ...
    result = fib();
    ...
    setTimeout(function() {
        console.log("Fib for " + n + " is " + result);
    },1000);
}

不要尝试实现“sleep”。在计算过程中不要暂停。


6
也许他正在为某种教育目的而设置算法,因此在计算步骤之间需要使用“sleep()”。换句话说,也许休眠是为了在网页上展示一些说明性动画。 - Pointy
@Pointy - 这正是我正在做的。 - Diazath
1
但是答案没有被处理,它运行、休眠、运行、休眠、运行、休眠...最后在结束时产生一个答案。也许问题可以更好地提出,并提供一些上下文。 - Josh Smeaton
有些情况下(例如付款),用户希望操作变慢,如果付款“太快”,他们会联系支持团队!因此,您需要人为地减缓操作速度... - allan.simon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接