JavaScript中的sleep()是什么?

3710

有没有比以下的pausecomp函数(取自这里)更好的方式在JavaScript中实现sleep?

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

这不是JavaScript中的休眠-操作之间的延迟的重复;我需要在函数执行过程中进行真正的休眠,而不是在某段代码执行之前延迟。


273
这是一个可怕的解决方案 - 在不做任何事情的同时,你将会消耗大量的处理周期。 - 17 of 26
21
睡眠的唯一目的就是轮询或等待回调 - setInterval 和 setTimeout 都比睡眠更有效。 - annakata
65
看到人们在不理解原帖想法的情况下就拒绝了,这真是令人惊讶。有时你需要一次__真正的睡眠__。我现在需要一次真正的睡眠来测试在顶层窗口和 iframe 之间发布和接收消息时浏览器的行为。用 while 循环来保持忙碌似乎是唯一的方法。 - Devs love ZenUML
2
我想使用 sleep() 来计时动画。(我知道有更好的方法...)提问者提供的代码在 Chrome 中对我无效,因为浏览器不会在脚本中进行修改后立即更新 DOM,而是等待代码执行完成后再进行任何 DOM 更新,因此脚本会等待所有延迟的总和,然后一次性应用所有 DOM 更新。 - Ari Fordsham
16
@DevsloveZenUML,浏览器环境的设计师和开发人员为了用户的利益决定不满足你们的要求,因为在异步应用程序中赋予某人阻止整个页面的明确能力是疯狂的。 - Oleg V. Volkov
显示剩余5条评论
94个回答

4

首先,setTimeout和setInterval是JavaScript的回调性质所决定的,因此应该使用它们。如果你想使用sleep()函数来控制代码流程或架构,则是不正确的。

话虽如此,我可以帮助提供两种实现sleep的方法。

1. 从头开始模拟同步运行:

// A module to do that //dual-license: MIT or WTF [you can use it anyhow and leave my nickname in a comment if you want to]
var _ = (function(){
  var queue = [];
  var play = function(){
    var go = queue.shift();
      if(go) {
        if(go.a) {
          go.f();
          play();
        }
        else
        {
          setTimeout(play, go.t);
        }
      }
  }
  return {
    go:function(f){
      queue.push({a:1, f:f});
    },
    sleep:function(t){
      queue.push({a:0, t:t});
    },
    playback:play
  }
})();

自动播放也应该是可能的。
// Usage

_.go(function(){

  // Your code
  console.log('first');

});

_.sleep(5000);

_.go(function(){

  // Your code
  console.log('next');

});

// This triggers the simulation
_.playback();

2. 真正的同步运行

有一天我仔细思考了一下,我认为在 JavaScript 中实现真正的睡眠只有一种技术方法。

一个睡眠函数必须是一个同步的 Ajax 调用,并设置一个超时时间等于睡眠值。这就是实现真正的 sleep() 的唯一方式。


4

这是一个阻塞式的睡眠函数。在需要顺序执行的 测试活动 中,本人发现这种方式更易于理解。可以像这样调用: sleep(2000),以便让线程沉睡2秒。

function sleep(ms) {
    const now = Date.now();
    const limit = now + ms;
    let execute = true;
    while (execute) {
        if (limit < Date.now()) {
            execute = false;
        }
    }
    return;
  }

4

这段代码取自这个链接,不会冻结计算机。但它只能在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);
    }
}

4
如果你像这样编写一个 sleep 函数:
var sleep = function(period, decision, callback){
    var interval = setInterval(function(){
        if (decision()) {
            interval = clearInterval(interval);
            callback();
        }
    }, period);
}

如果您有一个需要多次调用的异步函数,

var xhr = function(url, callback){
    // Make an Ajax request
    // Call a callback when the request fulfils
}

您需要按照以下方式设置您的项目:

var ready = false;

function xhr1(){
    xhr(url1, function(){ ready = true;});
}
function xhr2(){
    xhr(url2, function(){ ready = true; });
}
function xhr3(){
    xhr(url3, function(){ ready = true; });
}

然后你可以这样做:
xhr1();
sleep(100, function(){ return done; }, xhr2);
sleep(100, function(){ return done; }, xhr3);
sleep(100, function(){ return done; }, function(){
    // Do more
});

不再使用像这样无限嵌套的回调函数:

xhr(url1, function(){
    xhr2(url2, function(){
        xhr3(url3, function(){
            // Do more
        });
    });
});

4

天真地说,你可以使用while循环来实现sleep(),就像pausecomp函数一样(基本上是相同的):

const sleep = (seconds) => {
    const waitUntil = new Date().getTime() + seconds * 1000
    while(new Date().getTime() < waitUntil) {
        // do nothing
    }
}

而你可以像这样使用 sleep() 方法:

const main = () => {
    const a = 1 + 3

    // Sleep 3 seconds before the next action
    sleep(3)
    const b = a + 4

    // Sleep 4 seconds before the next action
    sleep(4)
    const c = b + 5
}

main()

这是我想象中使用sleep函数的方式,相对容易理解。我从另一篇文章JavaScript中的Sleep-操作之间的延迟借鉴,以展示您可能一直想要使用它。
不幸的是,您的计算机会变得很热,并且所有工作都将被阻塞。如果在浏览器中运行,选项卡将停滞不前,用户将无法与页面交互。
如果您重构代码为异步,则可以像其他文章一样利用setTimeout()作为睡眠函数。
// define sleep using setTimeout
const sleep = (seconds, callback) => setTimeout(() => callback(), seconds * 1000)

const main = () => {
    const a = 1 + 3
    let b = undefined
    let c = undefined

    // Sleep 3 seconds before the next action
    sleep(3, () => {
        b = a + 4

        // Sleep 4 seconds before the next action
        sleep(4, () => {
            c = b + 5
        })
    })
}

main()

正如你所说,这不是你想要的。我修改了来自JavaScript中的Sleep-操作之间的延迟的示例,以展示为什么可能会出现这种情况。随着你添加更多的操作,你将需要将逻辑分解为单独的函数或嵌套你的代码越来越深(回调地狱)。
为了解决“回调地狱”,我们可以使用promises定义sleep:
const sleep = (seconds) => new Promise((resolve => setTimeout(() => resolve(), seconds * 1000)))

const main = () => {
    const a = 1 + 3
    let b = undefined
    let c = undefined

    // Sleep 3 seconds before the next action
    return sleep(3)
        .then(() => {
            b = a + 4

            // Sleep 4 seconds before the next action
            return sleep(4)
        })
        .then(() => {
            c = b + 5
        })
}

main()

承诺可以避免深层嵌套,但仍不像我们开始的常规同步代码。我们希望编写看起来同步但没有任何缺点的代码。让我们再次使用async/await重写我们的主方法:
const sleep = (seconds) => new Promise((resolve => setTimeout(() => resolve(), seconds * 1000)))

const main = async () => {
    const a = 1 + 3

    // Sleep 3 seconds before the next action
    await sleep(3)
    const b = a + 4

    // Sleep 4 seconds before the next action
    await sleep(4)
    const c = b + 5
}

main()

使用 async/await,我们可以几乎像同步、阻塞函数一样调用 sleep()。这解决了您可能在其他帖子中使用回调解决方案时遇到的问题,并避免了长时间运行的循环引起的问题。

这是唯一对我起作用的解决方案。虽然我对此一窍不通,但我怀疑将main()变为async,以便我们可以使用async调用sleep可能就是关键所在。 - mherzog
这是唯一对我有效的解决方案。虽然我对此一窍不通,但我怀疑将main()设置为async,以便我们可以使用async调用sleep可能会起作用。 - undefined

3

如果您确实需要使用sleep()方法来测试某些功能,请注意,在调试过程中,它会导致浏览器崩溃 - 这可能正是您需要它的原因。在生产模式下,我将注释掉此函数。

function pauseBrowser(millis) {
    var date = Date.now();
    var curDate = null;
    do {
        curDate = Date.now();
    } while (curDate-date < millis);
}

不要在循环中使用 new Date(),除非你想浪费内存、处理能力、电池和设备寿命。


4
你的代码与问题中的代码几乎一模一样,该问题询问是否有更好的方法来完成这个任务。 - Matt Gibson
"This" 是相对于每个人而言的。问题的标题是:“如果我想要一个 JavaScript 版本的 sleep(),我该怎么办?”使用 setTimeout 或 setInterval 涉及不同的推理,这可能需要更多时间,因此并不总是选择,例如用于快速调试测试。此外,如果我没有错的话,在循环内部使用 Date.now() 要比 new Date() 更节省内存。 - Rodrigo

3

我需要一个忙等待来进行测试。我不想拆分代码,因为那会是很多工作,所以一个简单的for循环就足够了。

for (var i=0; i<1000000; i++){                  
    // Waiting
}

我认为这样做没有任何不利之处,而且对我来说很有效。


可能会被编译掉。 - Viktor Sehr
1
请不要这样做。这是一个阻塞睡眠调用,这意味着当您的代码独占JS线程时,没有其他JavaScript可以运行。 - Steve Midgley
7
“@SteveMidgley说‘在你的代码占用JS线程时,没有其他JavaScript可以运行’,这对我来说似乎正是OP要做的事情 ¯_(ツ)_/¯” - drigoangelo
2
我刚刚运行了一些测试,似乎即使是一个空循环也会阻塞浏览器和CPU(不仅仅是JavaScript)。而使用 for 循环几乎总是立即执行,无论 i 的最大值是多少,甚至在其中放置复杂的数学代码也是如此。因此,除非你只等待几毫秒,否则似乎仍然没有办法在JS中优雅地睡眠。 - Beejor

3

我更喜欢这个函数式风格一行代码sleep函数:

const sleep = (ms) => new Promise((res) => setTimeout(res, ms, ms));

// usage
async function main() {
  console.log("before");
  const t = await sleep(10_000); /* 10 sec */
  console.log("after " + t);
}
main();

5
这不是睡眠函数。如果你调用睡眠函数,它不会等待时间流逝,而是会立即执行下一条指令。 - Zeel Shah

2
使用 await 支持和 bluebird promise
await bluebird.delay(1000);

这将像 C 语言 中的同步 sleep(1) 一样工作。这是我最喜欢的解决方案。

如果您支持await,则不需要Bluebird。 - Dan Dascalescu

2
拥抱JavaScript的异步特性!
以下所有方法都会立即返回,但有一个单独的地方可以放置代码,以便在发生某些事件后运行。
我列出的这些方法都是针对不同用例的,并且大致按照它们的复杂程度排序。
不同的事情如下:
- 等待某些条件变为真 - 等待一组方法完成(任意顺序),然后调用单个回调函数 - 在调用回调函数之前,按特定顺序运行一系列具有共享状态的异步方法
等待
在没有可访问的回调告诉您某些操作已经完成时,等待查看某些条件是否为真非常有用。
这是一个相当基本的实现,假设该条件最终会变为真。通过一些微调,它可以扩展为更加有用的形式(例如,通过设置调用限制)。(我昨天才写了这个!)
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);
});

我在这里调用一个加载Ext JS“store”的东西,在store包含某些内容之前等待,然后才继续执行(beforeEachJasmine测试框架的一部分)。 等待多个操作完成 我还需要在多个方法完成加载后运行单个回调。您可以像这样实现:
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实现的重命名版本。我正在处理的很多东西都将回调作为最后一个参数,因此可以像这样简单地调用,而不必用额外的函数包装一切。

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