Что такое JavaScript-версия sleep ()? - PullRequest
1903 голосов
/ 04 июня 2009

Есть ли лучший способ создать sleep в JavaScript, чем следующая pausecomp функция ( взято отсюда )?

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

Это не дубликат Сон в JavaScript - задержка между действиями ; Мне нужен реальный сон в середине функции, а не задержка перед выполнением фрагмента кода.

Ответы [ 73 ]

2 голосов
/ 29 августа 2016

С поддержкой await и обещанием синей птицы :

await bluebird.delay(1000);

Это будет работать как синхронный sleep(1) язык c. Мое любимое решение.

1 голос
/ 01 июня 2019

2019 Обновление с использованием Atomics.wait

Должен работать в узле 9.3 или выше.

Мне нужен был довольно точный таймер в Node.js, и он прекрасно работает для этого. Однако кажется, что поддержка браузеров крайне ограничена.

let ms = 10000;
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);

Выполнил несколько 10-секундных тестов таймера.

С setTimeout я получаю ошибку до 7000 микросекунд. (7мс)

При использовании Atomics моя ошибка, по-видимому, не превышает 600 микросекунд. (0.6ms)

1 голос
/ 28 апреля 2014

Вы можете использовать закрывающий вызов setTimeout () с постепенно увеличивающимися значениями.

var items = ['item1', 'item2', 'item3'];

function functionToExecute(item) {
  console.log('function executed for item: ' + item);
}

$.each(items, function (index, item) {
  var timeoutValue = index * 2000;
  setTimeout(function() {
    console.log('waited ' + timeoutValue + ' milliseconds');
    functionToExecute(item);
  }, timeoutValue);
});

Результат:

waited 0 milliseconds
function executed for item: item1
waited 2000 milliseconds
function executed for item: item2
waited 4000 milliseconds
function executed for item: item3 
1 голос
/ 13 октября 2016

Проблема большинства решений заключается в том, что они перематывают стек. В некоторых случаях это может быть большой проблемой. В этом примере я показываю, как по-разному использовать итераторы для имитации реального сна

В этом примере генератор вызывает свой собственный next(), поэтому, как только он пойдет, он сам по себе.

var h=a();
h.next().value.r=h; //that's how U run it, best I came up with

//sleep without breaking 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");

}
1 голос
/ 28 марта 2016

Я ориентируюсь на решение в течение дня, но все еще думаю, как сохранить цепочку при использовании обратного вызова. Все знакомы с традиционным стилем программирования, в котором код выполняется построчно, синхронно. SetTimeout использует обратный вызов, поэтому следующая строка не ожидает его завершения. Это позволило мне подумать, как сделать его "синхронизированным", чтобы сделать функцию "сна".

Начиная с простой сопрограммы:

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

Я хочу поспать 3 секунды посередине, но не хочу доминировать над всем потоком, поэтому сопрограмма должна выполняться другим потоком. Я рассматриваю Unity YieldInstruction и изменяю сопрограмму следующим образом:

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 = 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;
}

После запуска coroutine1 (я тестировал в IE11 и Chrome49) вы увидите, что он спит 3 секунды между двумя операторами консоли. Он сохраняет коды так же красиво, как и традиционный стиль. Хитрость во сне для рутины. Он читает тело функции вызывающей стороны как строку и разбивает его на 2 части. Снимите верхнюю часть и создайте другую функцию нижней частью. После ожидания указанного количества миллисекунд он вызывает созданную функцию, применяя исходный контекст и аргументы. Для оригинального потока это закончится "возвращением" как обычно. За "доходность"? Он используется для сопоставления регулярных выражений. Это необходимо, но бесполезно.

Он не на 100% совершенен, но, по крайней мере, достигает моей работы. Я должен упомянуть некоторые ограничения в использовании этой части кода. Поскольку код разбивается на 2 части, оператор return должен быть во внешнем, а не в любом цикле или {}. т.е. * * 1 016

function coroutine3() {
    this.a = 100;
    console.log('coroutine3-1:start');
    if(true) {
        return sleepFor(3000).yield;
    } // <- raise 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;
}

Ожидает функцию проверки, пока не вернет истину. Он проверяет значение каждые 100 мс. Вы можете настроить его, передав дополнительный аргумент. Рассмотрим тестирование сопрограммы2:

function coroutine2(c) {
    /* some codes 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 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. Затем coroutine1 будет ждать 3000 мс. Coroutine2 войдет в шаг 2 после ожидания 500 мс. После этого он продолжит шаг 3, как только обнаружит значения coroutine1.a> 100.

Остерегайтесь того, что есть 3 контекста для хранения переменной "a". Один является внешним, значения которого равны 10 и 11. Другой находится в coroutine1, значения которого равны 100 и 101. Последний находится в coroutine2, значения которого равны 1,2 и 3. В coroutine2 он также ожидает ca, который приходит от coroutine1, пока его значение не станет больше 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 codes 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 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++);

Протестировано в IE11 и Chrome49. Поскольку он использует arguments.callee, поэтому может возникнуть проблема, если он будет работать в строгом режиме.

1 голос
/ 14 февраля 2012

Метод объекта, который должен использовать метод "сна", такой как следующий:

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" не может зависеть от этого. Поскольку «спящего» метода не существует, я использую более поздний метод и соответствующим образом оформляю свой код.

Надеюсь, это поможет.

1 голос
/ 31 марта 2016

Если вы действительно хотите приостановить выполнение сценария, вы можете сделать это:

var milliseconds;
var pretime;
var stage;

function step(time){
  switch(stage){
    case 0:
      //Code before the pause

      pretime=time;
      milliseconds=XXX;
      stage=1;
      break;
    case 1:
      //Code that is looped through while paused

      if(time-pretime >= milliseconds){
        //Code after the pause

        pretime=time;
        milliseconds=XXX;
        stage=2;
      }
      break;
    case 2:
      //Code that is looped through while paused

      if(time-pretime >= milliseconds){
        //Code after the pause

        pretime=time;
        milliseconds=XXX;
        stage=3;
      }
      break;
    case 3:
      //Etc...
  }

  Window.requestAnimationFrame(step)
}

step();

Это, вероятно, именно то, что вам нужно, если вы все равно используете цикл, и вы можете изменить его таким образом, чтобы у вас была псевдо-многопоточность, когда некоторые функции ожидают некоторое время, а другие работают нормально. Я использую это все время для игр с чистым JS.

1 голос
/ 09 марта 2016

Есть новая библиотека, которая аккуратно объединяет функции вместе с таймаутами, чтобы вы могли избежать ада обратного вызова.

Sequencr.js

Превращает это:

setTimeout(function(timeout){
    function1();
    setTimeout(function(timeout){
        function2();
        setTimeout(function(timeout){
            function3();
        }, timeout, timeout)
    }, timeout, timeout)
}, 10, 10);

в это:

Sequencr.chain([function1, function2, function3], 10);

И имеет встроенную поддержку циклов, которые «спят» между каждой итерацией.

1 голос
/ 26 сентября 2011

Если вы хотите перевести анонимную функцию, например, созданную вами в качестве обработчика, я рекомендую следующее:

function()
{
if (!wait_condition)
    {
    setTimeout(arguments.callee, 100, /*comma-separated arguments here*/);
    }
//rest of function
}

Этот код говорит: «Если условие ожидания еще не выполнено, снова вызовите эту функцию с этими аргументами». Я использовал этот метод для передачи одних и тех же аргументов моим обработчикам, что фактически делает этот код незапрашивающим sleep () (который работает только в начале вашей функции).

0 голосов
/ 14 октября 2016

На стороне сервера вы можете использовать метод deasync sleep(), который изначально реализован в C , поэтому он может эффективно реализовать эффект wait без блокирование цикла обработки событий или загрузка процессора на 100%.

Пример:

#!/usr/bin/env node

// Requires `npm install --save deasync`
var sleep = require("deasync").sleep;

sleep(5000);

console.log ("Hello World!!");

Но если вам нужна функция javascript pure (например, для запуска ее на стороне клиента через браузер), мне жаль говорить, что ваша функция pausecomp() является Единственный способ приблизиться к нему и, более того:

  1. Это приостанавливает не только вашу функцию, но и весь цикл обработки событий. Поэтому никакие другие события не будут посещаться.

  2. Он загружает ваш процессор на 100%.

Итак, если вам нужен скрипт для браузера и вы не хотите этих ужасных эффектов, я должен сказать, что вы должны переосмыслить свою функцию таким образом:

а). Вы можете вспомнить его (или вызвать функцию do_the_rest()) за время ожидания. Более простой способ, если вы не ожидаете какого-либо результата от вашей функции.

* 1 034 * б). Или, если вам нужно дождаться результата, вам следует перейти к использованию обещаний (или, конечно же, к обратному вызову; -)).

Пример не ожидается:

function myFunc() {

    console.log ("Do some things");

    setTimeout(function doTheRest(){
        console.log ("Do more things...");
    }, 5000);

    // Returns undefined.
};


myFunc();

Пример возврата обещания (обратите внимание, что это меняет использование вашей функции):

function myFunc(someString) {

    return new Promise(function(resolve, reject) {

        var result = [someString];
        result.push("Do some things");

        setTimeout(function(){
            result.push("Do more things...");
            resolve(result.join("\n"));
        }, 5000);

    });

};


// But notice that this approach affect to the function usage...
// (It returns a promise, not actual data):
myFunc("Hello!!").then(function(data){
    console.log(data);
}).catch(function(err){
    console.error(err);
});
...