Таймер с меняющимся периодом JavaScript

от автора

Доброго времени суток, коллеги. Не так давно в рамках одного проекта появилась задача генерировать события от таймера, с изменяющимся периодом, кроме того желательно было реализовать возможность перепровязки обработчика событий таймера. Задача была связала с тем, что нужно было отрисовывать полилинию движения человека на карте опираясь на GoogleMaps API в зависимости от скорости движения человека по местности.

Немножко теории

JavaScript событийно-ориентированный язык программирования, он поддерживает асинхронные, что иногда может приводить к забавным казусам.

var S = 'Вася молодец'; setTimeout(alert(S), 1000); S = 'Вася дурак'; 

Если вы откроете консоль и запустите этот фрагмент кода, к большому сожалению Васи, вывалится алерт ‘Вася дурак’. Всё дело в том, что код переданный в метод setTimeout будет выполнен через 1 секунду, а переопределение S произойдёт сразу после создания таймера. А для того, чтобы таких досадных казусов не происходило js поддерживает механизм замыканий closure — объявление функции внутри функции. В этом случае объявленная функция имеет доступ к данным внешней функции, кроме того, замыкая функцию внутри асинхронной мы можем гарантировать, что следующая строчка будет выполнена за предыдущей.

var S = 'Вася молодец'; setTimeout(function () {     alert(S);     S = 'Вася дурак'; }, 1000);  

Такой код гарантирует, что переопределение переменной произойдёт после того как появится алерт.

Решение

Суть решения моей проблемы заключается в замыкании метода вызывающего setTimeout в setTimeout. В самом простом виде решение выглядит так:

var period = 10;  function  nonLinearTimer() {     setTimeout(nonLinearTimer, period);     onNonLinearTimerEvent(); }  function  onNonLinearTimerEvent() = {      // реализация обработчика }  

Очевидно onNonLinearTimerEvent() будет вызываться через промежутки времени равные period. Но как я уже говорил хотелось бы иметь возможность менять поведение обработчика событий, к тому же хотелось бы иметь возможность отключить таймер, то есть перестать получать от него события. Для начала завернём то что получилось в отдельный объект:

nonLinearTimer = {     _period : 0,     _onNonLinearTimerEvent : null, }  nonLinearTimer.Start = function() {     setTimeout(nonLinearTimer.Start, nonLinearTimer._period);     nonLinearTimer._onNonLinearTimerEvent(); }  nonLinearTimer.setHandler = function(f) { 	this._onNonLinearTimerEvent = f; }  nonLinearTimer.setPeriod = function(p) { 	this._period = p; } 

Теперь мы можем динамически менять поведение обработчика и период использую сеттеры. Но осталась не решена проблема с остановкой таймера. Очевидно что нужно переопределить поведение Start, чтобы она не вызывала сама себя. Для этого определим приватную функцию _empty и при остановке будем подменять вызов Start на вызов _empty:

nonLinearTimer = {     _period : 1000,     _onNonLinearTimerEvent : null,     _empty : function () { 	},     _mode : null }  nonLinearTimer.Start = function() { 	setTimeout(nonLinearTimer._mode, nonLinearTimer._period);         nonLinearTimer._onNonLinearTimerEvent(); }  nonLinearTimer.Stop = function() {     this._mode = this._empty; }  nonLinearTimer.Prepare = function() { 	this._mode = this.Start; }  nonLinearTimer.setHandler = function(f) { 	this._onNonLinearTimerEvent = f; }  nonLinearTimer.setPeriod = function(p) { 	this._period = p; } 

Готово. Теперь вызывая метод Stop мы можем останавливать таймер.

Я добился своей цели и человечек ходит по карте с разной скоростью как я и хотел. Другой пример использования такого таймера — эмуляция ускоренного движения: достаточно менять период прямо внутри обработчика, например так:

myTimer.setPeriod(500 / tick); tick++; 

Пример использования таймера можно посмотреть здесь
Исходный код скачать вот здесь

З.Ы. Прошу строго не судить, если знаете какое-то существующее решение — я его не искал. Задача решалась в рамках хакатона часа в 4 ночи, а потому было принято решение не тратить время на поиск готовых решений, а сразу писать 🙂

ссылка на оригинал статьи http://habrahabr.ru/post/180209/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *