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

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

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


当前回答

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

每个人都熟悉传统的编程风格,即以同步的方式逐行运行代码。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,所以如果在严格模式下运行可能会有麻烦。

其他回答

在LiveScript(编译为JavaScript)中,可以执行以下操作:

sleep = (ms, func) -> set-timeout func, ms

console.log "hello-1"
<- sleep 2000ms
console.log "hello-2"
<- sleep 2000ms
console.log "hello-3"

我同意其他海报。忙着睡觉是个坏主意。

但是,setTimeout不支持执行。它在超时设置后立即执行函数的下一行,而不是在超时过期后执行,因此无法完成睡眠所能完成的任务。

方法是将你的功能分解为前后两部分。

function doStuff()
{
  // Do some things
  setTimeout(continueExecution, 10000) // Wait ten seconds before continuing
}

function continueExecution()
{
   // Finish doing things after the pause
}

确保你的函数名仍然准确地描述了每一块正在做的事情(即GatherInputThenWait和CheckInput,而不是funcPart1和funcPart2)

此方法实现了在超时之后才执行您决定的代码行的目的,同时仍然将控制权返回到客户端PC,以执行它排队的任何其他代码。

正如评论中指出的那样,这绝对不会在循环中工作。你可以做一些花哨的(丑陋的)黑客来让它在一个循环中工作,但总的来说,这只会导致灾难性的意大利面条代码。

如果必须处理同步执行,我可以理解睡眠函数的用途。setInterval和setTimeout函数创建一个并行执行线程,该线程将执行序列返回到主程序,如果您必须等待给定的结果,这是无效的。当然,可以使用事件和处理程序,但在某些情况下,这不是预期的。

从该链接获取的代码不会冻结计算机。但它只在Firefox中有效。

/**
 * Netscape compatible WaitForDelay function.
 * You can use it as an alternative to Thread.Sleep() in any major programming language
 * that support it while JavaScript it self doesn't have any built-in function to do such a thing.
 * parameters:
 * (Number) delay in millisecond
 */
function nsWaitForDelay(delay) {
    /**
     * Just uncomment this code if you're building an extension for Firefox.
     * Since Firefox 3, we'll have to ask for user permission to execute XPCOM objects.
     */
    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");

    // Get the current thread.
    var thread = Components.classes["@mozilla.org/thread-manager;1"].getService(Components.interfaces.nsIThreadManager).currentThread;

    // Create an inner property to be used later as a notifier.
    this.delayed = true;

    /* Call JavaScript setTimeout function
      * to execute this.delayed = false
      * after it finishes.
      */
    setTimeout("this.delayed = false;", delay);

    /**
     * Keep looping until this.delayed = false
     */
    while (this.delayed) {
        /**
         * This code will not freeze your browser as it's documented in here:
         * https://developer.mozilla.org/en/Code_snippets/Threads#Waiting_for_a_background_task_to_complete
         */
        thread.processNextEvent(true);
    }
}

这里大多数解决方案的问题是它们倒带堆栈。在某些情况下,这可能是一个大问题。在这个例子中,我展示了如何以不同的方式使用迭代器来模拟真实的睡眠。

在本例中,生成器正在调用自己的next(),因此一旦它启动,它就自己运行了。

var h = a();
h.next().value.r = h; // That's how you run it. It is the best I came up with

// Sleep without breaking the stack!!!
function *a(){
    var obj = {};

    console.log("going to sleep....2s")

    setTimeout(function(){obj.r.next();}, 2000)
    yield obj;

    console.log("woke up");
    console.log("going to sleep no 2....2s")
    setTimeout(function(){obj.r.next();}, 2000)
    yield obj;

    console.log("woke up");
    console.log("going to sleep no 3....2s")

    setTimeout(function(){obj.r.next();}, 2000)
    yield obj;

    console.log("done");
}