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

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

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


当前回答

如果您真的想完全阻塞主线程并防止事件循环从事件队列中拉出,那么这里有一个很好的方法可以做到这一点,而不需要创建任何函数、新的Date对象或泄漏任何变量。我知道这个愚蠢的问题已经有一百万个答案了,但我没有看到有人使用这个精确的解决方案。这仅适用于现代浏览器。

警告:这不是你会投入生产的东西。它只是有助于理解浏览器事件循环。它可能对任何测试都没有用处。它不像一个正常的系统睡眠函数,因为JavaScript运行时仍然在每个循环中工作。

for (let e = performance.now() + 2000; performance.now() < e; ) {}

这里使用setTimeout回调,即使它几乎立即进入事件队列,也要在至少两秒后才能调用:

setTimeout(function() {
  console.log("timeout finished");
}, 0);

for (let e = performance.now() + 2000; performance.now() < e; ) {}
console.log("haha wait for me first");

您将经历大约两秒的暂停,然后看到:

haha wait for me first
timeout finished

在Date.now()上使用performance.now(()的好处是Date对象是

受到时钟偏斜和系统时钟调整的影响。这个时间的价值可能不总是单调增加随后的值可以减小或保持不变。*

通常,performance.now()更适合高精度地测量时间差异。

使用for循环的好处是可以在运行之前设置块的本地变量。这允许您在循环外进行加法运算,同时仍然是“单行”。这应该可以最大限度地减少这种热循环燃烧的CPU负载。

其他回答

需要使用“休眠”方法的对象的方法,如下所示:

function SomeObject() {
    this.SomeProperty = "xxx";
    return this;
}
SomeObject.prototype.SomeMethod = function () {
    this.DoSomething1(arg1);
    sleep(500);
    this.DoSomething2(arg1);
}

几乎可以翻译为:

function SomeObject() {
    this.SomeProperty = "xxx";
    return this;
}
SomeObject.prototype.SomeMethod = function (arg1) {
    var self = this;
    self.DoSomething1(arg1);
    setTimeout(function () {
        self.DoSomething2(arg1);
    }, 500);
}

不同之处在于,“SomeMethod”操作在执行操作“DoSomething2”之前返回。“SomeMethod”的调用者不能依赖于此。由于“睡眠”方法不存在,我使用后一种方法并相应地设计代码。

使用三种功能:

调用setInterval启动循环的函数一个函数,它调用clearInterval来停止循环,然后调用setTimeout来休眠,最后在setTimeout内调用作为回调来重新启动循环一个循环,跟踪迭代次数,设置睡眠次数和最大次数,一旦达到睡眠次数就调用睡眠函数,并在达到最大次数后调用clearInterval

var foo={};函数main(){“使用严格”;/*初始化全局状态*/foo.bar=foo.bar ||0;/*初始化计时器*/foo.bop=设置间隔(foo.baz,1000);}睡眠=功能(计时器){“使用严格”;clearInterval(计时器);timer=setTimeout(function(){main()},5000);};foo.baz=函数(){“使用严格”;/*更新状态*/foo.bar=数字(foo.bar+1)||0;/*日志状态*/console.log(foo.bar);/*检查状态并在10时停止*/(foo.bar===5)&&睡眠(foo.bop);(foo.bar==10)&&clearInterval(foo.bop);};main();

工具书类

使用JavaScript开发游戏为什么iOS 8中的滚动事件更改是一件大事Ember.js中的实时轮询系统使用requestAnimationFrame()驱动动画MDN:JavaScript并发模型和事件循环网格研究:Node.js在JavaScript中超过60fps第2部分:不阻塞单个线程的CPU密集型JavaScript计算

使用更好的DX实现更安全的异步睡眠

我使用睡眠进行调试。。。我忘了用wait太多次了。让我挠头。我在写下面的实时片段时忘记使用await。。。不再!

如果您在1毫秒内运行了两次,下面的睡眠功能会提醒您。如果您确定使用了await,它还支持传递一个相当的参数。它不会投掷,因此可以安全地用作睡眠的替代品。享受

是的,实时片段中有一个JavaScript版本。

// Sleep, with protection against accidentally forgetting to use await
export const sleep = (s: number, quiet?: boolean) => {
  const sleepId = 'SLEEP_' + Date.now()
  const G = globalThis as any
  if (G[sleepId] === true && !quiet) {
    console.error('Did you call sleep without await? use quiet to hide msg.')
  } else {
    G[sleepId] = true
  }
  return new Promise((resolve) => {
    !quiet && setTimeout(() => {
      delete G[sleepId]
    }, 1)
    setTimeout(resolve, (s * 1000) | 0)
  })
}

//睡眠,防止意外忘记使用等待常量睡眠=(s,安静)=>{constsleepId='SLEEP_'+Date.now()const G=全局此if(G[sleepId]==true&&!quiet){console.error('您是否在没有等待的情况下调用sleep?使用quiet隐藏消息。')}其他{G[sleepId]=真}return new Promise((resolve)=>{!quiet&&setTimeout(()=>{删除G[sleepId]}, 1)setTimeout(解析,(s*1000)|0)})}常量main=async()=>{console.log('休眠1秒…')等待睡眠(1)console.log('使用等待的目标…')睡眠(99)睡眠(99)等待睡眠(1,true)console.log(“开发人员快乐!”)}main()

与公认更容易阅读的相比:

const sleep = (s: number) =>
  new Promise((p) => setTimeout(p, (s * 1000) | 0))

拥抱JavaScript的异步特性!

以下所有内容都将立即返回,但只有一个地方可以放置发生事件后要运行的代码。

我在这里概述的方法都适用于不同的用例,并根据其复杂性大致排序。

不同之处如下:

等待某些条件变为现实在调用单个回调之前,等待一组方法完成(按任何顺序)在调用回调之前,以特定顺序运行一系列具有共享状态的异步方法

Wait

如果没有任何可访问的回调来告诉您什么时候完成了执行,那么等待某个条件是否为真是非常有用的。

这是一个非常基本的实现,它假设条件在某个时刻变为真。通过一些调整,它可以扩展到更有用(例如,通过设置呼叫限制)。(我昨天才写了这篇!)

function waitFor(predicate, successCallback) {
    setTimeout(function () {
        var result = predicate();
        if (result !== undefined)
            successCallback(result);
        else
            waitFor(predicate, successCallback);
    }, 100);
}

呼叫代码:

beforeEach(function (done) {
    selectListField('A field');

    waitFor(function () {
        var availableOptions = stores.scrapeStore(optionStore);
        if (availableOptions.length !== 0)
            return availableOptions;
    }, done);
});

在这里,我调用了加载ExtJS“store”的东西,并等待该存储包含一些东西后再继续(beforeEach是Jasmine测试框架的东西)。

等待几件事完成

我还需要在加载完不同的方法后运行一个回调。你可以这样做:

createWaitRunner = function (completionCallback) {
    var callback = completionCallback;
    var completionRecord = [];
    var elements = 0;

    function maybeFinish() {
        var done = completionRecord.every(function (element) {
            return element === true
        });

        if (done)
            callback();
    }

    return {
        getNotifier: function (func) {
            func = func || function (){};

            var index = elements++;
            completionRecord[index] = false;

            return function () {
                func.applyTo(arguments);
                completionRecord[index] = true;
                maybeFinish();
            }
        }
    }
};

呼叫代码:

var waiter = createWaitRunner(done);

filterList.bindStore = waiter.getNotifier();
includeGrid.reconfigure = waiter.getNotifier(function (store) {
    includeStore = store;
});

excludeGrid.reconfigure = waiter.getNotifier(function (store) {
    excludeStore = store;
});

您可以只等待通知,也可以包装使用传递给函数的值的其他函数。调用所有方法后,将运行done。

按顺序运行异步方法

当我在一行中调用一系列异步方法时(同样在测试中),我使用了不同的方法。这有点类似于您可以在Async库中得到的东西——series也做了同样的事情,我先读了一下这个库,看看它是否符合我的要求。我认为我的测试有一个更好的API(实现起来很有趣!)。

// Provides a context for running asynchronous methods synchronously
// The context just provides a way of sharing bits of state
// Use 'run' to execute the methods.  These should be methods that take a callback and optionally the context as arguments
// Note the callback is provided first, so you have the option of just partially applying your function to the arguments you want
// instead of having to wrap even simple functions in another function

// When adding steps you can supply either just a function or a variable name and a function
// If you supply a variable name then the output of the function (which should be passed into the callback) will be written to the context
createSynchronisedRunner = function (doneFunction) {
    var context = {};

    var currentPosition = 0;
    var steps = [];

    // This is the loop. It is triggered again when each method finishes
    var runNext = function () {
        var step = steps[currentPosition];
        step.func.call(null,
                       function (output) {
                           step.outputHandler(output);
                           currentPosition++;

                           if (currentPosition === steps.length)
                               return;

                           runNext();
                       }, context);
    };

    var api = {};

    api.addStep = function (firstArg, secondArg) {
        var assignOutput;
        var func;

        // Overloads
        if (secondArg === undefined) {
            assignOutput = function () {
            };
            func = firstArg;
        }
        else {
            var propertyName = firstArg;
            assignOutput = function (output) {
                context[propertyName] = output;
            };
            func = secondArg;
        }

        steps.push({
            func: func,
            outputHandler: assignOutput
        });
    };

    api.run = function (completedAllCallback) {
        completedAllCallback = completedAllCallback || function(){};

        var lastStep = steps[steps.length - 1];
        var currentHandler = lastStep.outputHandler;
        lastStep.outputHandler = function (output) {
            currentHandler(output);
            completedAllCallback(context);
            doneFunction();
        };

        runNext();
    };

    // This is to support more flexible use where you use a done function in a different scope to initialisation
    // For example, the done of a test but create in a beforeEach
    api.setDoneCallback = function (done) {
        doneFunction = done;
    };

    return api;
};

呼叫代码:

beforeAll(function (done) {
    var runner = createSynchronisedRunner(done);
    runner.addStep('attachmentInformation', testEventService.getAttachmentCalled.partiallyApplyTo('cat eating lots of memory.jpg'));
    runner.addStep('attachment', getAttachment.partiallyApplyTo("cat eating lots of memory.jpg"));
    runner.addStep('noAttachment', getAttachment.partiallyApplyTo("somethingElse.jpg"));
    runner.run(function (context) {
        attachment = context.attachment;
        noAttachment = context.noAttachment;
    });
});

这里的PartiallyApplyTo基本上是Douglas Crockford实现Curry的重命名版本。我正在处理的很多东西都将回调作为最后的参数,这样简单的调用就可以这样完成,而不必用一个额外的函数来包装一切。

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

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