Cleartimeout()

Summary

  • Methods and allow us to run the once/regularly after milliseconds.
  • To cancel the execution, we should call with the value returned by .
  • Nested calls are a more flexible alternative to , allowing us to set the time between executions more precisely.
  • Zero delay scheduling with (the same as ) is used to schedule the call “as soon as possible, but after the current script is complete”.
  • The browser limits the minimal delay for five or more nested call of or for (after 5th call) to 4ms. That’s for historical reasons.

Please note that all scheduling methods do not guarantee the exact delay.

For example, the in-browser timer may slow down for a lot of reasons:

  • The CPU is overloaded.
  • The browser tab is in the background mode.
  • The laptop is on battery.

All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings.

Значения параметров

Параметр
Описание
function
Функция, которая будет многократно выполняться после истечения таймера.
delay
Время в миллисекундах, которое таймер должен ждать перед выполнением указанной функции или кода. Если этот параметр опущен, то используется значение 0, оно означает, что выполнение должно произойти немедленно, или, точнее, как можно скорее (как только завершат работу все обработчики событий). Если этот параметр меньше 4, то используется значение 4

Обратите внимание, что в любом случае фактическая задержка может быть больше, чем предполагалось, причины задержек перечислены ниже в примерах.
param1, …, paramX
Дополнительные параметры, передаваемые функции, указанной функцией или кодом, по истечении таймера. Передача дополнительных параметров функции в первом синтаксисе не работает в Internet Explorer 9 и ниже

Если вы хотите включить эту функцию в этом браузере, необходимо использовать полифилл.
code
Альтернативный синтаксис, который позволяет включать строку вместо функции, которая компилируется и выполняется по истечении таймера. Этот синтаксис не рекомендован к использованию в связи с угрозой безопасности.

Программная анимация: setInterval() в ActionScript

Продолжим тему программной анимации в ActionScript и сегодня более подробно рассмотрим использование метода setInterval().

Как уже говорилось, метод setInterval используется для многократного вызова (выполнения) пользовательских функций через установленный интервал времени. У этого метода два обязательных параметра:

— первый параметр — это имя пользовательской (то есть, созданной программистом) функции, которую предстоит периодически вызывать;

— второй параметр — это длительность задержки между вызовами функции.

Приведём простейший пример…

Код ActionScript 2.0:

или

Код ActionScript 3.0:

В данном примере метод setInterval используется для того, чтобы каждую секунду выполнять пользовательскую функцию myFunc

Обратите внимание, что интервал между вызовами пользовательских функций задаётся в миллисекундах (1 секунда = 1000 миллисекунд). Пользовательская функция myFunc в данном примере выводит в панель Output строку «hello», но это сделано исключительно для наглядности и простоты

На самом деле, в тело функции помещаются математические вычисления или манипуляции с объектами на сцене flash-ролика, которые необходимо выполнять с заданной частотой.

Приведём простой пример. Нарисуем методами динамического рисования стрелку и будем поворачивать её каждую секунду на 6 градусов (то есть за минуту стрелка сделает оборот на 360 градусов).

Код ActionScript 2.0:

или

Код ActionScript 3.0:

Результат:

Думаю, пока всё просто и понятно. Но на практике часто возникает необходимость не только запустить setInterval, но и остановить его. Например, пользовательская функция периодически вызывается с помощью setInterval до того момента, пока удовлетворяет какому-то условию. Как только условие нарушено, требуется остановить вызов функции setInterval-ом. Именно для таких случаев и предназначен метод clearInterval().

Для того, чтобы воспользоваться методом clearInterval, надо предварительно методу setInterval дать идентификатор. Иначе как clearInterval узнает, какой именно setInterval надо остановить (одновременно в флеш-приложении может работать несколько setInterval-ов). После того, как у setInterval-а появится свой идентификатор, остановить этот setInterval можно в любой момент, вызвав метод clearInterval и передав этому методу в качестве единственного параметра ранее созданный идентификатор. Ммм… надеюсь, понятно объяснил. Но гораздо понятнее это показать на примере…

Код ActionScript 2.0:

или

Код ActionScript 3.0:

В приведённом выше примере имеется три переменных:

— max и count — переменные, хранящие целые числа;

— intervalId — переменная-идентификатор для setInterval.

Метод setInterval каждую секунду вызывает пользовательскую функцию myFunc, а функция myFunc каждый раз увеличивает значение переменной count (count++). Когда значение переменной count становится больше значения переменной max, вызывается метод clearInterval с идентификатором intervalId в качестве единственного параметра. После вызова clearInterval периодический вызов пользовательской функции myFunc прекращается. Что и следовало ожидать.

Как я уже говорил, у метода setInterval всего два обязательных параметра. Но кроме этих двух обязательных, он может принимать и необязательные параметры. Используется это не очень часто, но иногда бывает полезно. Приведём пример…

Код ActionScript 2.0:

или

Код ActionScript 3.0:

В результате выполнения приведённого выше кода, мы получим в панель Output примерно следующее:

Как видите, всё довольно просто 🙂

Социальные закладки:

Комментарии:

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

Nested setTimeout

There are two ways of running something regularly.

One is . The other one is a nested , like this:

The above schedules the next call right at the end of the current one .

The nested is a more flexible method than . This way the next call may be scheduled differently, depending on the results of the current one.

For instance, we need to write a service that sends a request to the server every 5 seconds asking for data, but in case the server is overloaded, it should increase the interval to 10, 20, 40 seconds…

Here’s the pseudocode:

And if the functions that we’re scheduling are CPU-hungry, then we can measure the time taken by the execution and plan the next call sooner or later.

Nested allows to set the delay between the executions more precisely than .

Let’s compare two code fragments. The first one uses :

The second one uses nested :

For the internal scheduler will run every 100ms:

Did you notice?

The real delay between calls for is less than in the code!

That’s normal, because the time taken by ‘s execution “consumes” a part of the interval.

It is possible that ‘s execution turns out to be longer than we expected and takes more than 100ms.

In this case the engine waits for to complete, then checks the scheduler and if the time is up, runs it again immediately.

In the edge case, if the function always executes longer than ms, then the calls will happen without a pause at all.

And here is the picture for the nested :

The nested guarantees the fixed delay (here 100ms).

That’s because a new call is planned at the end of the previous one.

Garbage collection and setInterval/setTimeout callback

When a function is passed in , an internal reference is created to it and saved in the scheduler. It prevents the function from being garbage collected, even if there are no other references to it.

For the function stays in memory until is called.

There’s a side-effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don’t need the scheduled function anymore, it’s better to cancel it, even if it’s very small.

Очередь событий

Произошло одновременно несколько событий или во время работы одного случилось другое – как главному потоку обработать это?

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

Поэтому используется альтернативный подход.

Когда происходит событие, оно попадает в очередь.

Внутри браузера непрерывно работает «главный внутренний цикл», который следит за состоянием очереди и обрабатывает события, запускает соответствующие обработчики и т.п.

Иногда события добавляются в очередь сразу пачкой.

Например, при клике на элементе генерируется несколько событий:

  1. Сначала – нажата кнопка мыши.
  2. Затем – кнопка мыши отпущена и, так как это было над одним элементом, то дополнительно генерируется (два события сразу).

В действии:

Таким образом, при нажатии кнопки мыши в очередь попадёт событие , а при отпускании – сразу два события: и . Браузер обработает их строго одно за другим: → → .

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

JavaScript

JS Array
concat()
constructor
copyWithin()
entries()
every()
fill()
filter()
find()
findIndex()
forEach()
from()
includes()
indexOf()
isArray()
join()
keys()
length
lastIndexOf()
map()
pop()
prototype
push()
reduce()
reduceRight()
reverse()
shift()
slice()
some()
sort()
splice()
toString()
unshift()
valueOf()

JS Boolean
constructor
prototype
toString()
valueOf()

JS Classes
constructor()
extends
static
super

JS Date
constructor
getDate()
getDay()
getFullYear()
getHours()
getMilliseconds()
getMinutes()
getMonth()
getSeconds()
getTime()
getTimezoneOffset()
getUTCDate()
getUTCDay()
getUTCFullYear()
getUTCHours()
getUTCMilliseconds()
getUTCMinutes()
getUTCMonth()
getUTCSeconds()
now()
parse()
prototype
setDate()
setFullYear()
setHours()
setMilliseconds()
setMinutes()
setMonth()
setSeconds()
setTime()
setUTCDate()
setUTCFullYear()
setUTCHours()
setUTCMilliseconds()
setUTCMinutes()
setUTCMonth()
setUTCSeconds()
toDateString()
toISOString()
toJSON()
toLocaleDateString()
toLocaleTimeString()
toLocaleString()
toString()
toTimeString()
toUTCString()
UTC()
valueOf()

JS Error
name
message

JS Global
decodeURI()
decodeURIComponent()
encodeURI()
encodeURIComponent()
escape()
eval()
Infinity
isFinite()
isNaN()
NaN
Number()
parseFloat()
parseInt()
String()
undefined
unescape()

JS JSON
parse()
stringify()

JS Math
abs()
acos()
acosh()
asin()
asinh()
atan()
atan2()
atanh()
cbrt()
ceil()
cos()
cosh()
E
exp()
floor()
LN2
LN10
log()
LOG2E
LOG10E
max()
min()
PI
pow()
random()
round()
sin()
sqrt()
SQRT1_2
SQRT2
tan()
tanh()
trunc()

JS Number
constructor
isFinite()
isInteger()
isNaN()
isSafeInteger()
MAX_VALUE
MIN_VALUE
NEGATIVE_INFINITY
NaN
POSITIVE_INFINITY
prototype
toExponential()
toFixed()
toLocaleString()
toPrecision()
toString()
valueOf()

JS OperatorsJS RegExp
constructor
compile()
exec()
g
global
i
ignoreCase
lastIndex
m
multiline
n+
n*
n?
n{X}
n{X,Y}
n{X,}
n$
^n
?=n
?!n
source
test()
toString()

(x|y)
.
\w
\W
\d
\D
\s
\S
\b
\B
\0
\n
\f
\r
\t
\v
\xxx
\xdd
\uxxxx

JS Statements
break
class
continue
debugger
do…while
for
for…in
for…of
function
if…else
return
switch
throw
try…catch
var
while

JS String
charAt()
charCodeAt()
concat()
constructor
endsWith()
fromCharCode()
includes()
indexOf()
lastIndexOf()
length
localeCompare()
match()
prototype
repeat()
replace()
search()
slice()
split()
startsWith()
substr()
substring()
toLocaleLowerCase()
toLocaleUpperCase()
toLowerCase()
toString()
toUpperCase()
trim()
valueOf()

JS Уроки

JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS ScopeJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS DebuggingJS HoistingJS Strict ModeJS this KeywordJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON

setTimeout

The syntax:

Parameters:

Function or a string of code to execute.
Usually, that’s a function. For historical reasons, a string of code can be passed, but that’s not recommended.
The delay before run, in milliseconds (1000 ms = 1 second), by default 0.
, …
Arguments for the function (not supported in IE9-)

For instance, this code calls after one second:

With arguments:

If the first argument is a string, then JavaScript creates a function from it.

So, this will also work:

But using strings is not recommended, use arrow functions instead of them, like this:

Pass a function, but don’t run it

Novice developers sometimes make a mistake by adding brackets after the function:

That doesn’t work, because expects a reference to a function. And here runs the function, and the result of its execution is passed to . In our case the result of is (the function returns nothing), so nothing is scheduled.

A call to returns a “timer identifier” that we can use to cancel the execution.

The syntax to cancel:

In the code below, we schedule the function and then cancel it (changed our mind). As a result, nothing happens:

As we can see from output, in a browser the timer identifier is a number. In other environments, this can be something else. For instance, Node.js returns a timer object with additional methods.

Again, there is no universal specification for these methods, so that’s fine.

For browsers, timers are described in the of HTML5 standard.

Синтаксис

Согласно , функция setTimeout имеет следующий синтаксис:

где:

  • timeoutID — числовой идентификатор, который может использоваться функцией clearTimeout() для отмены таймера.
  • func — функция, которая будет выполнена.
  • code (альтернативный синтаксис) — блок кода, который будет выполнен.
  • delay — задержка в миллисекундах, по истечении которой будет выполнена функция. По умолчанию равна 0.

setTimeout и window.setTimeout

Вы заметили, что в систаксисе выше используется window.setTimeout. Почему?

Ну, setTimeout и window.setTimeout — это по сути одно и то же, разница лишь в том, что во втором случае мы ссылаемся на setTimeout как на свойство глобального объекта window.

На мой взгляд, это лишь добавляет сложности, небольшое преимущество — если вы определите где-то альтернативный метод setTimeout, который будет расположен раньше в цепочке областей видимости, у вас будут большие проблемы.

В примерах я буду опускать window, какой синтаксис использовать — решать вам.

JavaScript

JS Array
concat()
constructor
copyWithin()
entries()
every()
fill()
filter()
find()
findIndex()
forEach()
from()
includes()
indexOf()
isArray()
join()
keys()
length
lastIndexOf()
map()
pop()
prototype
push()
reduce()
reduceRight()
reverse()
shift()
slice()
some()
sort()
splice()
toString()
unshift()
valueOf()

JS Boolean
constructor
prototype
toString()
valueOf()

JS Classes
constructor()
extends
static
super

JS Date
constructor
getDate()
getDay()
getFullYear()
getHours()
getMilliseconds()
getMinutes()
getMonth()
getSeconds()
getTime()
getTimezoneOffset()
getUTCDate()
getUTCDay()
getUTCFullYear()
getUTCHours()
getUTCMilliseconds()
getUTCMinutes()
getUTCMonth()
getUTCSeconds()
now()
parse()
prototype
setDate()
setFullYear()
setHours()
setMilliseconds()
setMinutes()
setMonth()
setSeconds()
setTime()
setUTCDate()
setUTCFullYear()
setUTCHours()
setUTCMilliseconds()
setUTCMinutes()
setUTCMonth()
setUTCSeconds()
toDateString()
toISOString()
toJSON()
toLocaleDateString()
toLocaleTimeString()
toLocaleString()
toString()
toTimeString()
toUTCString()
UTC()
valueOf()

JS Error
name
message

JS Global
decodeURI()
decodeURIComponent()
encodeURI()
encodeURIComponent()
escape()
eval()
Infinity
isFinite()
isNaN()
NaN
Number()
parseFloat()
parseInt()
String()
undefined
unescape()

JS JSON
parse()
stringify()

JS Math
abs()
acos()
acosh()
asin()
asinh()
atan()
atan2()
atanh()
cbrt()
ceil()
cos()
cosh()
E
exp()
floor()
LN2
LN10
log()
LOG2E
LOG10E
max()
min()
PI
pow()
random()
round()
sin()
sqrt()
SQRT1_2
SQRT2
tan()
tanh()
trunc()

JS Number
constructor
isFinite()
isInteger()
isNaN()
isSafeInteger()
MAX_VALUE
MIN_VALUE
NEGATIVE_INFINITY
NaN
POSITIVE_INFINITY
prototype
toExponential()
toFixed()
toLocaleString()
toPrecision()
toString()
valueOf()

JS OperatorsJS RegExp
constructor
compile()
exec()
g
global
i
ignoreCase
lastIndex
m
multiline
n+
n*
n?
n{X}
n{X,Y}
n{X,}
n$
^n
?=n
?!n
source
test()
toString()

(x|y)
.
\w
\W
\d
\D
\s
\S
\b
\B
\0
\n
\f
\r
\t
\v
\xxx
\xdd
\uxxxx

JS Statements
break
class
continue
debugger
do…while
for
for…in
for…of
function
if…else
return
switch
throw
try…catch
var
while

JS String
charAt()
charCodeAt()
concat()
constructor
endsWith()
fromCharCode()
includes()
indexOf()
lastIndexOf()
length
localeCompare()
match()
prototype
repeat()
replace()
search()
slice()
split()
startsWith()
substr()
substring()
toLocaleLowerCase()
toLocaleUpperCase()
toLowerCase()
toString()
toUpperCase()
trim()
valueOf()

Определение и применение

JavaScript метод setInterval() объекта WindowOrWorkerGlobalScope многократно вызывает функцию или выполняет фрагмент кода с фиксированной задержкой времени между каждым вызовом.

Обращаю Ваше внимание на то, что функция, вызванная методом setInterval() многократно вызывает функцию с определенной задержкой, если Вам необходимо один раз вызвать функцию с определенной задержкой, то используйте метод setTimeout(). Метод setInterval() возвращает положительное числовое целое ненулевое значение, которое определяет таймер, это значение может быть передано методу clearInterval(), чтобы отменить выполнение программного кода, ранее отложенного вызовом метода setInterval()

Метод setInterval() возвращает положительное числовое целое ненулевое значение, которое определяет таймер, это значение может быть передано методу clearInterval(), чтобы отменить выполнение программного кода, ранее отложенного вызовом метода setInterval().

Гарантируется, что идентификатор тайм-аута никогда не будет повторно использован последующим вызовом setInterval(), или методом setTimeout() для того же объекта (окна или рабочего объекта), однако, разные объекты используют отдельные пулы идентификаторов.

Пул идентификаторов, используемых методами setTimeout() и setInterval() являются общими, что означает, что вы можете технически использовать методы clearTimeout() и clearInterval() взаимозаменяемо. Однако для ясности вам следует избегать этого.

Метод setInterval

Метод setInterval работает следующим образом:
первым параметром она принимает имя функции (без кавычек и круглых скобок),
а вторым параметром — через какой промежуток запускать эту функцию.
Второй параметр задается в миллисекундах (1000 миллисекунд = 1 секунда).

Пример: window.setInterval(timer, 1000) — этот код будет запускать
функцию timer раз в секунду. И каждую секунду эта функция timer будет выполнять какие-то
полезные операции, например, увеличивать счетчик на странице.

Давайте рассмотрим нужный для этого код. Пусть у нас дан инпут.
Будем каждую секунду увеличивать значение его атрибута value на единицу:

Обратите внимание на функцию parseInt —
она преобразует строку из атрибута value в число.

Это нужно, так как атрибут всегда отдает строку,
даже если там хранится число, как у нас, то есть elem.value вернет
‘1’, а не 1 (в самом начале таймера, когда в атрибуте еще 1).
И получится, что elem.value + 1 это ‘1’+1, что дает ’11’, а не 2).

Поэтому, если не применять parseInt — скрипт начнет складывать наши числа как строки
и мы увидим, что в инпуте сначала будет 1, потом 11, потом 111 и так далее.

Если же написать parseInt, то в инпуте будет сначала 1, потом 2, потом 3 и так далее.

JavaScript

JS Array
concat()
constructor
copyWithin()
entries()
every()
fill()
filter()
find()
findIndex()
forEach()
from()
includes()
indexOf()
isArray()
join()
keys()
length
lastIndexOf()
map()
pop()
prototype
push()
reduce()
reduceRight()
reverse()
shift()
slice()
some()
sort()
splice()
toString()
unshift()
valueOf()

JS Boolean
constructor
prototype
toString()
valueOf()

JS Classes
constructor()
extends
static
super

JS Date
constructor
getDate()
getDay()
getFullYear()
getHours()
getMilliseconds()
getMinutes()
getMonth()
getSeconds()
getTime()
getTimezoneOffset()
getUTCDate()
getUTCDay()
getUTCFullYear()
getUTCHours()
getUTCMilliseconds()
getUTCMinutes()
getUTCMonth()
getUTCSeconds()
now()
parse()
prototype
setDate()
setFullYear()
setHours()
setMilliseconds()
setMinutes()
setMonth()
setSeconds()
setTime()
setUTCDate()
setUTCFullYear()
setUTCHours()
setUTCMilliseconds()
setUTCMinutes()
setUTCMonth()
setUTCSeconds()
toDateString()
toISOString()
toJSON()
toLocaleDateString()
toLocaleTimeString()
toLocaleString()
toString()
toTimeString()
toUTCString()
UTC()
valueOf()

JS Error
name
message

JS Global
decodeURI()
decodeURIComponent()
encodeURI()
encodeURIComponent()
escape()
eval()
Infinity
isFinite()
isNaN()
NaN
Number()
parseFloat()
parseInt()
String()
undefined
unescape()

JS JSON
parse()
stringify()

JS Math
abs()
acos()
acosh()
asin()
asinh()
atan()
atan2()
atanh()
cbrt()
ceil()
cos()
cosh()
E
exp()
floor()
LN2
LN10
log()
LOG2E
LOG10E
max()
min()
PI
pow()
random()
round()
sin()
sqrt()
SQRT1_2
SQRT2
tan()
tanh()
trunc()

JS Number
constructor
isFinite()
isInteger()
isNaN()
isSafeInteger()
MAX_VALUE
MIN_VALUE
NEGATIVE_INFINITY
NaN
POSITIVE_INFINITY
prototype
toExponential()
toFixed()
toLocaleString()
toPrecision()
toString()
valueOf()

JS OperatorsJS RegExp
constructor
compile()
exec()
g
global
i
ignoreCase
lastIndex
m
multiline
n+
n*
n?
n{X}
n{X,Y}
n{X,}
n$
^n
?=n
?!n
source
test()
toString()

(x|y)
.
\w
\W
\d
\D
\s
\S
\b
\B
\0
\n
\f
\r
\t
\v
\xxx
\xdd
\uxxxx

JS Statements
break
class
continue
debugger
do…while
for
for…in
for…of
function
if…else
return
switch
throw
try…catch
var
while

JS String
charAt()
charCodeAt()
concat()
constructor
endsWith()
fromCharCode()
includes()
indexOf()
lastIndexOf()
length
localeCompare()
match()
prototype
repeat()
replace()
search()
slice()
split()
startsWith()
substr()
substring()
toLocaleLowerCase()
toLocaleUpperCase()
toLowerCase()
toString()
toUpperCase()
trim()
valueOf()

Вложенные (синхронные) события

Обычно возникающие события «становятся в очередь».

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

Рассмотрим в качестве примера событие .

Когда посетитель фокусируется на элементе, возникает событие . Обычно оно происходит, когда посетитель кликает на поле ввода, например:

Но ту же фокусировку можно вызвать и явно, вызовом метода :

В главе Фокусировка: focus/blur мы познакомимся с этим событием подробнее, а пока – нажмите на кнопку в примере ниже.

При этом обработчик вызовет метод на текстовом поле . Код обработчика , который при этом запустится, сработает синхронно, прямо сейчас, до завершения .

При клике на кнопке в примере выше будет видно, что управление вошло в , затем перешло в , затем вышло из .

Исключение в IE

Так ведут себя все браузеры, кроме IE.

В нём событие – всегда асинхронное, так что будет сначала полностью обработан клик, а потом – фокус. В остальных – фокус вызовется посередине клика. Попробуйте кликнуть в IE и в другом браузере, чтобы увидеть разницу.

Подводя итог

Одна из потенциальных ловушек, которую нужно иметь в виду, заключается в том, что setTimeout является асинхронным, она ставит функцию в очередь и запускает, когда текущий стек вызовов закончит выполнение. Все это выполняется в одном потоке (в связи с однопоточной природой JavaScript).

Существует также некоторая путаница при использовании нативной функции setTimeout и метода .

Метод delay добавляет определенную задержку между вызовами методов, нет возможности отменить задержку. Например, если вы хотите сделать изображение видимым, подождать пять секунд и снова скрыть его, вы можете использовать delay следующим образом:

Для всего остального лучше использовать setTimeout.

Наконец, если вам нужно несколько раз выполнить код после определенной задержки, лучше подойдет функция setInterval. Вы можете прочитаь о ней подробнее .

Ссылка на основную публикацию