Лучшей точности и производительности таймеры в JS


Обычные таймеры в JavaScript (setInteval & setTimeout) есть две проблемы:

  1. Они не столько точность, сколько вы можете сделать в JS. Если вы установите это так:

    setInterval(fn, 1000);
    

    Вы не можете быть уверены, что он будет вызываться каждые 1000 мс. Это будет называться 1000 + 1-10мс или даже больше, если бы некоторые тяжелые задачи были перед галочку.

    Это нормальное поведение. Плохая вещь об этом является то, что эти задержки будут накапливаться.

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

Обе проблемы могут быть решены с помощью webworker и некоторые переменные. Вот почему я создал класс для этого и нужен комментарий.

Мой класс также обеспечить анти-блокирование механизма. Когда не было тяжелых задач, стоящих перед тикать таймер, и нам нужно разобраться с задержкой - таймер восполнить все утерянные клещей, но это может быть "навсегда" петли. Чтобы предотвратить это, вы можете установить Макс мс для "изготовления утраченных тики" и после них будет перерыв на другой код/события/и т. д. и вернуться к работе.

module.exports = UberTimer = function UberTimer(callback, interval, blockThreadMaxMs) {
  this.callback = callback;
  this.interval = interval;

  this.lastTickTime = -1;

  this.blocking = {};
  this.blocking.maxTime = blockThreadMaxMs;
  this.blocking.startTime = -1;
}

////////////////////////////////////////////////////////////////////////

UberTimer.worker = null;
UberTimer.instances = [];

UberTimer.__startWorker = function() {
  if ( UberTimer.worker ) return;

  var blobURL = URL.createObjectURL( new Blob([ '(', (

    function() {
      setInterval(() => self.postMessage(0), 1);
    }

  ).toString(), ')()' ], { type: 'application/javascript' } ) );

  UberTimer.worker = new Worker(blobURL);
  UberTimer.worker.onmessage = UberTimer.__tick;
}

UberTimer.__stopWorker = function() {
  UberTimer.worker.terminate();
  UberTimer.worker = null;
}

UberTimer.__tick = function(timer) {
  for ( var timer of UberTimer.instances )
    timer.__workerTick();
}

UberTimer.create = function(callback, interval, blockThreadMaxMs=100) {
  var timer = new UberTimer(callback, interval, blockThreadMaxMs);

  UberTimer.instances.push(timer);
  UberTimer.__startWorker();

  return timer;
}

UberTimer.release = function(timer) {
  var i = UberTimer.instances.indexOf(timer)
  if ( i ) UberTimer.instances.splice(i, 1);

  if ( UberTimer.instances.length == 0 )
    UberTimer.__stopWorker();
}

////////////////////////////////////////////////////////////////////////

UberTimer.prototype.__makeCall = function() {
  this.callback();
  this.lastTickTime += this.interval;
}

UberTimer.prototype.__workerTick = function() {
  if ( this.lastTickTime == -1 ) return;

  var now = performance.now();

  if ( this.blocking.startTime == -1 )
    this.blocking.startTime = now;

  var elapsedTime = now - this.lastTickTime;

  if ( elapsedTime >= this.interval ) {
    this.__makeCall();

    var blockingTime = now - this.blocking.startTime;

    if ( blockingTime < this.blocking.maxTime )
      this.__workerTick();
    else
      this.blocking.startTime = -1;
  }
}

UberTimer.prototype.start = function() {
  this.__makeCall();
  this.lastTickTime = performance.now();
}

UberTimer.prototype.stop = function() {
  this.lastTickTime = -1;
}

Использование:

var timer = UberTimer.create(callback, intevalInMs, optional: maxBlockingTime);

timer.start();
timer.stop();

UberTimer.release(timer);


154
1
задан 31 марта 2018 в 09:03 Источник Поделиться
Комментарии
1 ответ

Ошибки

Первая какая-то логика и ошибки потока.


  1. Функция release использует indexOf чтобы получить таймер индекса i затем вы используете заявление if(i) { ... которые не удается удалить первый элемент в массив экземпляров. В результате работник не закрыли.

  2. Звоню UberTimer.stop(timer) внутри функции обратного вызова timer.callback не удается остановить таймер, обратный вызов продолжает называться. Потому что вы установили lastTickTime до -1 код потом пытается догнать, вызывая обратного вызова на полную ставку работника сообщения.

И связано с тем, как вы вызываете функцию обратного вызова.


  1. Если функция обратного вызова timer.callback не возвращает (выдает ошибку) на функцию timer.start в timer.lastTickTime свойство не задано и остается инит значение -1. Обратный вызов после вызова в быстрой последовательности, и если обратного вызова продолжает бросать его просто Тринг, чтобы догнать каждый раз, когда работник отправляет сообщение. Это в сущности бесконечное неполным циклом сообщений

  2. Потому что вы вызываете функцию обратного вызова функции timer.start любая ошибка в функции обратного вызова будет остановить текущий контекст исполнения. Это не то, что можно было бы ожидать от таймера.

  3. Когда у вас есть много таймеров. Если обратный вызов бросает вызовы (выше на instance массива) не будет называться UberTimer.__tick не добраться до завершения таймера итерации цикла.

Опасность, потенциал молчит об ошибках.

Потому что собственность UberTimer.worker подвергается существует опасность, что вы потеряете ссылку на рабочий, если он случайно или намеренно затирают. Это может привести к молчаливым ошибка, которая делает свой путь, чтобы освободить снижая производительность. Вы должны сделать это безопаснее (защита государства).

Стиль заметки


  • Подчеркнуть префиксы не создавать приватные / защищенные переменные. Если вы хотите, чтобы защитить состояние вашего объекта не подвергайте функции и свойства, которые вы не хотите связываться.

  • С подобных языков позволяют ленивых программистов, чтобы сохранить миллисекунд набора текста, не включая {...} для одной линии блоков операторов. Ирония заключается в том, что стоимость часов охоты баг. Глаза не ищут { и } когда вы думаете, вы ищете логику ошибки.

    Для кодирования вменяемости всегда разграничивают блоки с {...}


if(foo === blah) 
foo = 1;

// then later you make the change

if(foo === blah)
foo = 1;
blah = 1;

// Yes obvious in isolation. But in 100's of lines of code with many unblocked statements
// the eye sees the indent and the mind puts in the {}

// never an issue if you always do
if (foo === blah) { foo = 1 }
// or
if (foo === blah) {
foo = 1;
}


  • Использовать === или !== никогда не используйте == или !=

  • Для переменных, которые не меняются объявлять их с const

  • Не определить переменную в одном типе, только чтобы конвертировать его в другой, когда вы используете его. Функция работника может быть определена как константа строка.

  • Не добавляйте ненужного или лишнего кода, он только снижает качество кода. module.exports = UberTimer = function UberTimer( переназначить UberTimer не служит никакой цели в коде.


 this.blocking = {};
this.blocking.maxTime = blockThreadMaxMs;
this.blocking.startTime = -1;

 // should be

this.blocking = {
maxTime : blockThreadMaxMs,
startTime : -1,
};


  • Использовать объект функции стенографии, если вы можете.


UberTimer.prototype.__makeCall = function() { ... }

UberTimer.prototype.__workerTick = function() { ... }

UberTimer.prototype.start = function() { ... }

UberTimer.prototype.stop = function() { ... }


Лучше

UberTimer.prototype = {
__makeCall() { ... },
__workerTick() { ... },
start() { ... },
stop() { ... },
};

2
ответ дан 1 апреля 2018 в 05:04 Источник Поделиться