Javascript中的sleep()函数

15

假设出于某种奇怪的原因,我想在某段时间内阻止Javascript执行,我该怎么做。JS没有sleep()方法。请不要说使用while()循环,因为那样做是不好的。我可以使用window.showModalDialog,在模态对话框中放置一个带有非常短时间setTimeout的 window.close,以便用户不会注意到对话框。这将像小时间段的睡眠一样,如果需要,我可以多次调用它。是否有其他方法?

具体而言,我的用例是HTML5 SQL数据库提供了异步api,但我想将其用于一个小型Web应用程序,其存储永远不会很大。因此,由于小存储器上的查询将在客户端上运行,因此不需要异步API。因此,我想编写具有同步API的ORM,以便开发人员可以更轻松地使用它。为了将这个异步转换为同步API,我需要类似sleep的东西。


1
你能详细说明一下你的例子吗?听起来像是你需要一个 setInterval(),但我对你的使用情况不是100%清楚。 - Nick Craver
6个回答

15

window.setTimeoutwindow.setInterval 是你唯一的朋友。

使用setTimeout递归调用一个设置另一个延迟时间的函数的示例如下:

function go() {
    if (go.count < 4) {
        // logs 1, 2, 3 to firebug console at 1 second intervals
        console.log(go.count++);
        window.setTimeout(go, 1000);
    }
}
go.count = 1;

go();

如果您需要在定时器结束之前清除它,可以选择捕获timeoutID并将其与window.clearTimeout一起使用。

请注意,window.setTimeoutwindow.setInterval都不会阻止其他脚本的执行 - 我认为这在JavaScript的当前形式下是不可能的。有一些编码方式可以模拟UI阻塞,例如使用showModalDialog或具有一些全局的blocking布尔值,但这已经是最接近实现阻塞效果的方法了。


4
这已经在问题中了 :) - Nick Craver
1
-1:这已经在问题中了。还有谁点赞了这个? - amara
1
是的,这基本上是JavaScript中Thread.Sleep()/插入语言等效的答案。 - Russ Cam
7
我点赞了它。仅仅因为它在问题中并不意味着它不是答案。这个回答说“基本上就是你的唯一朋友”,这提供了更多关于可以使用什么以及介绍了一个新功能的数据。 - Kerry Jones
1
@nomind 这是Javascript的做法,因为Javascript完全是关于回调和传递函数的。 "阻塞"不是在Javascript中的正确做法,因为它会阻塞整个浏览器,而不仅仅是你的脚本。采用语言的开发技术,不要把语言塞进你所知道的东西里。Javascript不是一种过程式语言。 - deceze
显示剩余5条评论

9

@Russ Cam是正确的,setTimeout是您要寻找的内容。 但是,您在问题中提到它的方式让我觉得可能存在一些关于它的使用方式的混淆。

它不会阻止所在块的执行,而是在一定时间间隔后调用其输入函数。 举个例子:

function illustration() {
  // start out doing some setup code
  alert("This part will run first");

  // set up some code to be executed later, in 5 seconds (5000 milliseconds):
  setTimeout(function () {
    alert("This code will run last, after a 5 second delay")
  }, 5000);

  // This code will run after the timeout is set, but before its supplied function runs:
  alert("This will be the second of 3 alert messages to display");
}

当您运行此函数时,您将看到三个警报消息,在第二和第三之间有一段延迟时间:
  1. “这部分将首先运行”
  2. “这将是要显示的3个警报消息中的第二个”
  3. “此代码将在5秒延迟后最后运行”

是的,我理解那个,但我想要阻止执行,所以setTimeout不能帮忙。 - nomind
1
如果你想阻止执行,那么 while 就是你想要的,但这会冻结浏览器(因为它正在阻止执行)。我怀疑你真的想阻止执行。 - pkaeding

6

需要澄清的是:这个问题是要完全停止脚本执行一段时间,而不是延迟某些代码的执行,这将由setTimeout或setInterval来完成。

在JavaScript中,没有办法实现一个干净的sleep()函数,也不应该这样做。

与某些人认为的不同,setTimout和setInterval并不会像暂停一样停止脚本执行。它们只是注册一个要延迟运行的函数,而脚本的其余部分将继续运行,同时等待超时/间隔的到来。

由于JavaScript的异步特性,阻止任何代码执行的唯一方法是非常丑陋且绝对不推荐使用的while循环,它会在给定的时间内运行。由于JavaScript本身在一个单线程中运行(我们不讨论WebWorkers API在这里...),这将阻止任何JS代码运行,直到该循环结束。但这确实是非常糟糕的风格...

如果您的程序无法在没有类似sleep()的东西的情况下工作,那么您可能应该重新考虑您的方法。


3

这个答案可能有点烦人,但请耐心看完 :-).

由于Javascript通常在浏览器内运行,而浏览器是多线程的,其中Javascript可能占用了多个线程,因此没有像单线程程序中那样的“全局休眠”概念。

如果您想以任何合理的方式暂停操作,可以考虑以下方法。

假设您想要执行以下操作

function foo(s) {
    var j = 1; var k = 1;
    sleep(s); // This would be nice right?  
    window.alert("Sum is : " + (j+k));
}

实际上变成:

function foo(s) {
     var j = 1 ; var k = 1;
     setTimeout(s, function() { 
            window.alert("Sum is : " + (j+k));
     });
}

当然问题在于从过程的角度来看,你可能希望有类似以下的内容:
function foo() {
   do stuff;
   pause();
   do more stuff;
   return;
}

function bar() {
      foo(10);
      window.alert("I want this to happen only after foo()");
}

问题在于,使用setTimeout并不能真正做到上面展示的那样。

但是你可以做这样的事情:

 function foo(s, afterwards, afterparms) {
     var j = 1 ; var k = 1;
     setTimeout(s, function() { 
            window.alert("Sum is : " + (j+k));
            afterparms.parm1 = afterparms.parm1.toUpperCase();
            afterwards(afterparms);
     });
 }

 function bar() {
      foo(10, function() {
          window.alert("This will happen after window.alert");
      }, { parm1: 'hello', parm2: 'world' } );
 }

请注意,以上仅是将信息传递给函数的一种方法,如果您查找函数调用约定、变量参数等内容,您可以做一些非常酷的事情。
如果您从程序化的角度思考,编程可能会很烦人。但是,有两件事情需要记住关于JavaScript。它既不是过程式语言,也不是面向对象语言。JavaScript是一种具有复杂事件管理模型的函数式语言。因此,您必须按照这些术语来思考。
这也是有道理的,因为典型的JavaScript机器是一个GUI(Web浏览器),通过设计不想完全阻止某些处理而尝试执行某些操作。想象一下,当页面正在进行某些操作时,浏览器完全锁定,您肯定会想掐死那个程序员。
JavaScript代码应该具有“fire-and-forget”的特性,如果必须等待事件发生才能继续,则应查看JavaScript事件模型。
虽然在处理一些琐碎的问题时这可能是不方便的编程方式,但很有可能您使用暂停等待的方式会遇到竞争条件,并且有更合适的方法来实现您尝试实现的“效果”。
如果您发布特定情况,可能会有更多信息提供,以解决这种情况。
干杯!

错误。JS不支持多线程运行。setTimeout也不能实现问题所要求的功能。请参考我的答案了解详情。 - selfawaresoup
JavaScript 是单线程的。许多浏览器也是如此。 - el.pescado - нет войне
感谢您的所有评论。如果这证明了需要使用sleep(),我已经更新了我的用例问题。 - nomind
我不同意你的评论,认为JavaScript既是面向对象的,也是过程化的 - 请参见https://dev59.com/pnVD5IYBdhLWcg3wDHDm。你的意思是它没有基于类的实现继承吗? - Russ Cam

3
我可以使用 window.showModalDialog 并在模态对话框中使用 setTimeout 设置非常短的时间来放置 window.close。
这很有创意,但它只允许用户与模态对话框中的任何内容进行交互的时间长度。浏览器的其余部分会像您调用了一个 brutal while (new Date().getTime()<t1); 循环一样挂起。
模态对话框对 CPU 周期更友好,并且将避免在等待时间较长的情况下触发脚本执行时间监视器,但您必须在烦人的闪烁式打开对话框框和非通用浏览器支持以及所有人都讨厌模态对话框的问题之间取得平衡!
这将类似于小时间段的睡眠,如果需要,我可以多次调用它。还有其他方法吗?
你到底想通过睡眠实现什么?除非返回事件循环或触发模态对话框,否则您不会获得任何屏幕更新或用户交互,因此我不知道内联睡眠对您有什么作用。您肯定不能以这种方式进行动画。
在Firefox中,你可以使用类似Python的生成器generators,这样可以使用yield编写过程式交互过程,必要时将控制返回给浏览器。这可以提高代码的简洁性,因为您不必将条件和循环结构重写为变量中的记忆状态。但是,由于只有Mozilla浏览器支持它,因此在可预见的未来内无法真正在实际应用中使用它。
ETA:

我想编写一个带有同步API的ORM,以便开发人员可以更轻松地使用它。为了将异步API与同步API桥接起来,我需要类似于sleep的东西。

抱歉,在一般情况下无法完成。基本的JavaScript没有线程、协程或其他可以桥接同步和异步代码的原语。
Web SQL数据库规范依赖于浏览器调用传递给executeSql()的结果处理函数作为浏览器回调。浏览器回调只能在控制权已经返回给浏览器时触发,而不能在同步执行线程中触发。
理论上,您可以打开一个 modalDialog,在 modalDialog 的窗口内执行异步的 Web SQL Database 调用,并使 modalDialog 将结果返回给调用方窗口脚本中的同步代码。然而,目前没有任何浏览器同时支持 openDatabase 和 openModalDialog!

查看更新后的问题中的用例,如果这样做是合理的话。但是我使用 showModalDialog 方法存在一些问题,即使在 10 毫秒内关闭它,它可能会在慢速机器上显示出来(这种情况可能吗?),而且焦点不会返回到父窗口的正确位置。 - nomind
@nomind:添加了无用的信息。也许将来的浏览器实现模态对话框和SQL结合起来可能是可行的,但除了弹出窗口的问题外,它还需要用户禁用他们的弹出窗口拦截器。我认为你只能使用异步代码和回调函数。或者只需使用更广泛支持的同步localStorage后端而不是奇特的openDatabase:对于具有对象样式访问的“小型Web应用程序”,关系数据库的特性根本不会帮助你。 - bobince
我不同意。任何基于 WebKit 的浏览器,例如 Google Chrome,都具有 showModalDialog 和 openDatabase。 - nomind

1
我知道这个问题有点老了,但我遇到了一个类似的问题,我需要模拟一个需要长时间加载的脚本。最终我通过创建一个服务器端点来解决这个问题,在发送响应之前等待5秒钟,然后将一个脚本标签添加到DOM中指向它。
尽管需要服务器端实现,但它允许您在任何特定点停止页面。正如其他人之前所说,这将阻塞整个浏览器,而不仅仅是您的脚本。

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