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

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

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


当前回答

拥抱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的重命名版本。我正在处理的很多东西都将回调作为最后的参数,这样简单的调用就可以这样完成,而不必用一个额外的函数来包装一切。

其他回答

如果(像我一样)你在Rhino中使用JavaScript,你可以使用。。。

try
{
  java.lang.Thread.sleep(timeInMilliseconds);
}
catch (e)
{
  /*
   * This will happen if the sleep is woken up - you might want to check
   * if enough time has passed and sleep again if not - depending on how
   * important the sleep time is to you.
   */
}

如果你喜欢建议,不要失去表现。setTimeout是您的预期睡眠。然而,如果您需要一种语法,其中代码被睡眠“中间分割”,我们可以这样做:

sleep = function(tm, fn){
   window.setTimeout(fn, tm);
}

然后,准备如下功能:

var fnBeforeSleep = function(){

  // All code before sleep

}

var fnAfterSleep = function(){

  // All code after sleep

}

然后:

fnBeforeSleep();
sleep(2000, fnAfterSleep);

// Yep! Syntactically, it is very close to:

fnBeforeSleep();
sleep(2000);
fnAfterSleep();

2009年的一个老问题。2015年,ECMAScript 2015 AKA ES6中定义的生成器可以提供新的解决方案。它于2015年6月获得批准,但之前已在Firefox和Chrome中实现。现在,休眠功能可以在不冻结浏览器的情况下,在循环和子函数中进行非繁忙、非阻塞和嵌套。只需要纯JavaScript,不需要库或框架。

下面的程序显示了sleep()和runSleepyTask()是如何实现的。sleep()函数只是一个yield语句。它非常简单,实际上直接编写yield语句比调用sleep()更容易,但这样就不会有sleep单词了:-)yield返回一个时间值给wakeup()内的next()方法并等待。实际的“睡眠”是使用旧的setTimeout()在wakeup()中完成的。在回调时,next()方法触发yield语句继续,yield的“魔力”在于所有局部变量及其周围的整个调用堆栈仍然完好无损。

使用sleep()或yield的函数必须定义为生成器。这很容易通过在关键字函数*中添加星号来实现。执行生成器有点棘手。当使用关键字new调用时,生成器返回一个具有next()方法的对象,但不执行生成器的主体(关键字new是可选的,没有任何区别)。next()方法触发生成器主体的执行,直到遇到产量。包装函数runSleepyTask()启动乒乓球:next()等待yield,yield等待next(()。

另一种调用生成器的方法是使用关键字yield*,这里它的工作方式类似于一个简单的函数调用,但它还包括返回next()的能力。

示例drawTree()演示了这一切。它在旋转的3D场景上绘制了一棵带有树叶的树。一棵树被画成树干,顶部有三个不同方向的部分。在短暂的睡眠后,通过递归调用drawTree(),将每个部分绘制为另一个较小的树。一棵很小的树被画成一片叶子。

每个叶子在一个单独的任务中都有自己的生命,该任务以runSleepyTask()开始。它在growtLeaf()中出生、生长、静止、褪色、坠落和死亡。速度由sleep()控制。这证明了多任务处理是多么容易。

函数*sleep(毫秒){yield毫秒};函数runSleepyTask(任务){(函数唤醒(){var result=task.next();if(!result.done)setTimeout(唤醒,result.value);})()}////////////////作者:Ole Middelboe/////////////////////////////pen3D=setup3D();var taskObject=新绘图树(pen3D.center,5);runSleepyTask(taskObject);函数*drawTree(root3D,大小){如果(size<2)运行SleepyTask(new-growLeaf(root3D))其他{pen3D.drawTrunk(root3D,大小);for(变量p为[1,3,5]){var part3D=新pen3D.Thing;root3D.add(part3D);part3D.移动(尺寸).转动(p).倾斜(1-p/20);产量*睡眠(50);产量*drawTree(part3D,(0.7+p/40)*尺寸);}}}函数*growtLeaf(stem3D){var leaf3D=pen3D.drawLeaf(stem3D);对于(var s=0;s++<15;){yield*sleep(100);leaf3D.scale.multiplyScalar(1.1)}yield*sleep(1000+9000*Math.random());对于(var c=0;c++<30;){yield*sleep(200);leaf3D.sskin.color.setRGB(c/30,1-c/40,0)}对于(var m=0;m++<90;){yield*sleep(50);leaf3D.turn(0.4).tilt(0.3).move(2)}leaf3D.visible=false;}///////////////////////////////////////////////////////////////////////函数setup3D(){var场景,相机,渲染器,directionalLight,pen3D;scene=新的THREE.scene();相机=新的三个透视相机(75,window.innerWidth/window.innerHeight,0.1,1000);相机位置设置(0,15,20);renderer=new THREE.WebGLRenderer({alpha:真,抗锯齿:真});renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(render.domElement);directionalLight=新的三个。directionalLight(0xffffaa,0.7);directionalLight.position.set(-1,2,1);scene.add(directionalLight);scene.add(新的THREE.AmbientLight(0x9999ff));(函数render(){requestAnimationFrame(渲染);//renderer.setSize(window.innerWidth,window.innerHeight);场景旋转Y(10/60/60);renderer.render(场景,摄影机);})();window.addEventListener窗口('调整大小',函数(){renderer.setSize(window.innerWidth,window.innerHeight);camera.aspx ect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();},假的);pen3D={drawTrunk:函数(根,大小){//root.skin=皮肤(0.5、0.3、0.2);root.add(新的三个网格(新的三维圆柱体几何体(大小/12,大小/10,大小16),root.skin).translateY(大小/2));root.add(新的THREE.Mesh(新的THEREE.SphereGeometry(size/12,16),root.skin).translateY(大小));返回根;},drawLeaf:功能(茎){stem.skin.color.setRGB(0,1,0);stem.add(新的三个网格(新的三维圆柱体几何体(0,0.02,0.6),茎.皮肤).旋转X(0.3).平移Y(0.3));stem.add(新的THREE.Mesh(新的THREE.CircleGeometry(0.2),茎.皮肤).旋转X(0.3).平移Y(0.4));返回阀杆;},事物:函数(){三.Object3D.调用(this);this.skin=新的THREE.MeshLambertMaterial({颜色:新三。颜色(0.5,0.3,0.2),vertexColors:THREE.FaceColors(顶点颜色),边:三边,双面})}};pen3D.Thing.prototype=对象创建(THREE.Object3D.prototype);pen3D.Thing.prototype.tilt=pen3D.Thing.prototype.rotateX;pen3D.Thing.prototype.turn=pen3D.Thing.prototype.rotateY;pen3D.Thing.prototype.move=pen3D.Thing.prototype.translateY;pen3D.center=新pen3D.Thing;scene.add(pen3D.center);返回pen3D;}<script src=“https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js“></script>

3D内容隐藏在setup3D()中,只是为了让它比console.log()少一些无聊。顺便说一下,角度是用弧度来测量的。

经测试可在Firefox和Chrome中运行。未在Internet Explorer和iOS(iPad)中实施。试着自己运行它。

在我再次找到答案后,加布里埃尔·拉特纳一年前对sleep()的JavaScript版本是什么做出了类似的回答?。

总结一下(就像前面的回答中所说的):

JavaScript中没有内置的睡眠函数。您应该使用setTimeout或setInterval来实现类似的效果。

如果你真的想,你可以用一个for循环来模拟睡眠功能,比如原始问题中所示的循环,但这会让你的CPU工作得很疯狂。在Web Worker内部,另一种解决方案是向非响应IP地址发出同步XMLHttpRequest,并设置适当的超时。这将避免CPU利用率问题。下面是一个代码示例:

//仅在web工作程序内部工作函数休眠(毫秒){var req=新XMLHttpRequest();请求打开(“GET”,“http://192.0.2.0/“,错误);req.timeout=毫秒;尝试{请求发送();}捕获(ex){}}console.log('休眠1秒…');睡眠(1000);console.log(“睡眠!”);console.log('休眠5秒…')睡眠(5000);console.log(“睡眠!”);

这里的大多数答案都是错误的,或者至少是过时的。没有理由JavaScript必须是单线程的,事实上也不是。今天,所有主流浏览器都支持工人。在此之前,Rhino和Node.js等其他JavaScript运行时支持多线程。

“JavaScript是单线程的”不是有效答案。例如,在工作线程中运行睡眠函数不会阻止UI线程中运行的任何代码。

在支持生成器和yield的较新运行时中,可以在单线程环境中为sleep函数带来类似的功能:

// This is based on the latest ES6 drafts.
// JavaScript 1.7+ (SpiderMonkey/Firefox 2+) syntax is slightly different

// Run code you want to sleep here (omit star if using JavaScript 1.7)
function* main(){
    for (var i = 0; i < 10; i++) {
        // To sleep for 10 milliseconds 10 times in a row
        yield 10;
    }

    yield 5;
    console.log('I just slept 5 milliseconds!');
}

// Resume the given generator after ms milliseconds
function resume(ms, generator){
    setTimeout(function(){
        // Omit .value if using JavaScript 1.7
        var nextSleep = generator.next().value;
        resume(nextSleep, generator);
    }, ms);
}

// Initialize a generator and get first sleep for the recursive function
var
    generator = main(),
    firstSleep = generator.next().value;

// Initialize recursive resume function
resume(firstSleep, generator);

这种对睡眠的模仿不同于真正的睡眠函数,因为它不会阻塞线程。它只是JavaScript当前setTimeout函数之上的糖。这种功能类型已经在Task.js中实现,现在应该可以在Firefox中使用。