有没有比下面的pausecomp函数(取自此处)更好的方法来设计JavaScript中的睡眠?

function pausecomp(millis)
{
    var date = new Date();
    var curDate = null;
    do { curDate = new Date(); }
    while(curDate-date < millis);
}

这不是JavaScript中的Sleep的重复-动作之间的延迟;我希望在函数的中间有一个真正的睡眠,而不是在代码执行之前有一段延迟。


当前回答

对于希望将循环执行的一组调用隔开的特定情况,可以使用类似于下面代码的原型。如果没有原型,可以用setTimeout替换延迟函数。

function itemHandler(item)
{
    alert(item);
}

var itemSet = ['a','b','c'];

// Each call to itemHandler will execute
// 1 second apart
for(var i=0; i<itemSet.length; i++)
{
    var secondsUntilExecution = i;
    itemHandler.delay(secondsUntilExecution, item)
}

其他回答

2019更新使用Atomics.wait

它应该在Node.js 9.3或更高版本中工作。

我在Node.js中需要一个非常精确的计时器,它非常适合。

然而,浏览器中的支持似乎非常有限。

设ms=10000;Atomics.wait(新Int32Array(新SharedArray Buffer(4)),0,0,ms);

运行了几次10秒计时器基准测试。

使用setTimeout,我得到的错误高达7000微秒(7毫秒)。

使用Atomics,我的错误似乎保持在600微秒(0.6毫秒)以下

2020年更新:总结

function sleep(millis){ // Need help of a server-side page
  let netMillis = Math.max(millis-5, 0); // Assuming 5 ms overhead
  let xhr = new XMLHttpRequest();
  xhr.open('GET', '/sleep.jsp?millis=' + netMillis + '&rand=' + Math.random(), false);
  try{
    xhr.send();
  }catch(e){
  }
}

function sleepAsync(millis){ // Use only in async function
  let netMillis = Math.max(millis-1, 0); // Assuming 1 ms overhead
  return new Promise((resolve) => {
    setTimeout(resolve, netMillis);
  });
}
function sleepSync(millis){ // Use only in worker thread, currently Chrome-only
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, millis);
}

function sleepTest(){
  console.time('sleep');
  sleep(1000);
  console.timeEnd('sleep');
}

async function sleepAsyncTest(){
  console.time('sleepAsync');
  await sleepAsync(1000);
  console.timeEnd('sleepAsync');
}

function sleepSyncTest(){
  let source = `${sleepSync.toString()}
    console.time('sleepSync');
    sleepSync(1000);
    console.timeEnd('sleepSync');`;
  let src = 'data:text/javascript,' + encodeURIComponent(source);
  console.log(src);
  var worker = new Worker(src);
}

其中服务器端页面,例如sleep.jsp,看起来像:

<%
try{
  Thread.sleep(Long.parseLong(request.getParameter("millis")));
}catch(InterruptedException e){}
%>

第一:

定义要执行的函数,如下所示:

function alertWorld(){
  alert("Hello, World!");
}

然后使用setTimeout方法安排其执行:

setTimeout(alertWorld, 1000)

注意两件事

第二个参数是以毫秒为单位的时间作为第一个参数,必须只传递函数的名称(引用),而不传递括号

如果您使用jQuery,实际上有人创建了一个“延迟”插件,它只不过是setTimeout的包装器:

// Delay Plugin for jQuery
// - http://www.evanbot.com
// - © 2008 Evan Byrne

jQuery.fn.delay = function(time,func){
    this.each(function(){
        setTimeout(func,time);
    });

    return this;
};

然后,您可以按预期在一行函数调用中使用它:

$('#warning')
.addClass('highlight')
.delay(1000)
.removeClass('highlight');

我浏览了一天的解决方案,但我仍在思考如何在使用回调时保持可链接性。

每个人都熟悉传统的编程风格,即以同步的方式逐行运行代码。SetTimeout使用回调,因此下一行不会等待它完成。这让我思考如何使其“同步”,从而实现“睡眠”功能。

从一个简单的协同程序开始:

function coroutine() {
    console.log('coroutine-1:start');
    sleepFor(3000); // Sleep for 3 seconds here
    console.log('coroutine-2:complete');
}

我想中间睡3秒钟,但我不想控制整个流程,所以协同程序必须由另一个线程执行。我考虑Unity Yield Instruction,并按以下方式修改协程:

function coroutine1() {
    this.a = 100;
    console.log('coroutine1-1:start');
    return sleepFor(3000).yield; // Sleep for 3 seconds here
    console.log('coroutine1-2:complete');
    this.a++;
}

var c1 = new coroutine1();

声明sleepFor原型:

sleepFor = function(ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?sleepFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    setTimeout(function() {
        new Function(funcArgs, funcBody).apply(context, args);
    }, ms);
    return this;
}

运行协同程序1(我在InternetExplorer11和Chrome49中进行了测试)后,您将看到它在两个控制台语句之间休眠3秒。它使代码与传统样式一样漂亮。

棘手的一点是在睡眠中。它将调用者函数体作为字符串读取,并将其分成两部分。拆下上部并通过下部创建另一个功能。等待指定的毫秒数后,它通过应用原始上下文和参数来调用创建的函数。对于原始流,它将像往常一样以“返回”结束。为了“收益”?它用于正则表达式匹配。这是必要的,但毫无用处。

它根本不是100%完美,但它至少实现了我的工作。我不得不提到使用这段代码时的一些限制。当代码被分成两部分时,“return”语句必须在外部,而不是在任何循环或{}中。即

function coroutine3() {
    this.a = 100;
    console.log('coroutine3-1:start');
    if(true) {
        return sleepFor(3000).yield;
    } // <- Raise an exception here
    console.log('coroutine3-2:complete');
    this.a++;
}

上述代码一定有问题,因为所创建的函数中不能单独存在右括号。另一个限制是“var xxx=123”声明的所有局部变量都不能传递到下一个函数。您必须使用“this.xxx=123”来实现相同的目标。如果您的函数有参数,并且它们发生了更改,则修改后的值也无法传递到下一个函数。

function coroutine4(x) { // Assume x=abc
    var z = x;
    x = 'def';
    console.log('coroutine4-1:start' + z + x); // z=abc, x=def
    return sleepFor(3000).yield;
    console.log('coroutine4-2:' + z + x); // z=undefined, x=abc
}

我将介绍另一个函数原型:waitFor

waitFor = function(check, ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?waitFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    var thread = setInterval(function() {
        if(check()) {
            clearInterval(thread);
            new Function(funcArgs, funcBody).apply(context, args);
        }
    }, ms?ms:100);
    return this;
}

它等待“check”函数,直到它返回true。它每100毫秒检查一次值。您可以通过传递额外的参数来调整它。考虑测试协程2:

function coroutine2(c) {
    /* Some code here */
    this.a = 1;
    console.log('coroutine2-1:' + this.a++);
    return sleepFor(500).yield;

    /* Next */
    console.log('coroutine2-2:' + this.a++);
    console.log('coroutine2-2:waitFor c.a>100:' + c.a);
    return waitFor(function() {
        return c.a>100;
    }).yield;

    /* The rest of the code */
    console.log('coroutine2-3:' + this.a++);
}

也是我们迄今为止喜爱的漂亮款式。实际上,我讨厌嵌套回调。很容易理解,协程2将等待协程1的完成。有趣的好的,然后运行以下代码:

this.a = 10;
console.log('outer-1:' + this.a++);
var c1 = new coroutine1();
var c2 = new coroutine2(c1);
console.log('outer-2:' + this.a++);

输出为:

outer-1:10
coroutine1-1:start
coroutine2-1:1
outer-2:11
coroutine2-2:2
coroutine2-2:waitFor c.a>100:100
coroutine1-2:complete
coroutine2-3:3

在初始化协程1和协程2后,立即完成外部。然后,协程1将等待3000毫秒。等待500毫秒后,子程序2将进入步骤2。之后,一旦检测到协程1.a值>100,它将继续执行步骤3。

请注意,有三种上下文可以保存变量“a”。一个是外部,值为10和11。另一个在协程1中,其值为100和101。最后一个在协程2中,其值为1、2和3。在协程2中,它还等待来自协程1的c.a,直到其值大于100。3个上下文是独立的。

复制和粘贴的完整代码:

sleepFor = function(ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?sleepFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    setTimeout(function() {
        new Function(funcArgs, funcBody).apply(context, args);
    }, ms);
    return this;
}

waitFor = function(check, ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?waitFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    var thread = setInterval(function() {
        if(check()) {
            clearInterval(thread);
            new Function(funcArgs, funcBody).apply(context, args);
        }
    }, ms?ms:100);
    return this;
}

function coroutine1() {
    this.a = 100;
    console.log('coroutine1-1:start');
    return sleepFor(3000).yield;
    console.log('coroutine1-2:complete');
    this.a++;
}

function coroutine2(c) {
    /* Some code here */
    this.a = 1;
    console.log('coroutine2-1:' + this.a++);
    return sleepFor(500).yield;

    /* next */
    console.log('coroutine2-2:' + this.a++);
    console.log('coroutine2-2:waitFor c.a>100:' + c.a);
    return waitFor(function() {
        return c.a>100;
    }).yield;

    /* The rest of the code */
    console.log('coroutine2-3:' + this.a++);
}

this.a = 10;
console.log('outer-1:' + this.a++);
var c1 = new coroutine1();
var c2 = new coroutine2(c1);
console.log('outer-2:' + this.a++);

它在Internet Explorer 11和Chrome 49中进行了测试。因为它使用arguments.callee,所以如果在严格模式下运行可能会有麻烦。

我也搜索了一个睡眠解决方案(不用于生产代码,只用于开发和测试),并找到了这篇文章:

JavaScript sleep()或wait()

…还有一篇关于客户端解决方案的文章:JavaScript睡眠

此外,当您调用alert()时,代码也会暂停,同时显示警报——您需要找到一种方法来不显示警报,但获得相同的效果。:)