Асинхронность в JavaScript. setTimeout и setInterval
На этом уроке мы познакомимся с таймерами, которые предназначены для вызова кода на языке JavaScript через определённые промежутки времени.
setTimeout и setInterval
В JavaScript имеются методы, которые позволяют вызвать функцию не сразу, а через некоторый промежуток времени (в асинхронном режиме). Называются они setTimeout
и setInterval
.
Отличаются они друг от друга лишь тем, что setTimeout
выполняет вызов функции всего один раз, а setInterval
– постоянно через указанный интервал времени.
Синтаксис setTimeout
и setInterval
:
// setTimeout
window.setTimeout(func [, delay] [, arg1, arg2, ...]);
// setInterval
window.setTimeout(func [, delay] [, arg1, arg2, ...]);
Параметры:
func
– функция, которую нужно вызвать спустя указанное вdelay
количество миллисекунд;delay
– количество миллисекунд по истечении которых нужно выполнитьfunc
;arg1, arg2, ...
– дополнительные аргументы, которые нужно передать вfunc
.
Например, вызовем функцию myFunc
один раз по прошествии 3 секунд:
// функция
function myFunc() {
console.log('after 3 seconds');
}
// вызовем функцию myFunc после 3 секунд
window.setTimeout(myFunc, 3000);
// выведем сообщение в консоль
console.log('immediately');
При выполнение этого кода программа не остановится на месте, где мы зарегистрировали некоторую асинхронность посредством setTimeout
и не будет блокировать её дальнейшее выполнение на 3 секунды, через которые нужно будет вызвать myFunc
. Выполнение скрипта продолжится дальше и сначала браузер выведет в консоль сообщение «immediately», а затем через 3 секунды – «after 3 seconds».
Другими словами, когда браузер доходит до исполнения setTimeout
, он как бы помещает функцию myFunc
в некоторое место, а затем по прошествии определённого количества времени её вызывает. При этом блокировка основного потока выполнения программы не происходит.
Чтобы более подробно разобраться в этом необходимо рассмотреть, как движок JavaScript выполняет синхронный и асинхронный код, а также что такое event loop и как он работает.
Синхронный и асинхронный код
Но перед тем, как переходить к асинхронными операциям, следует сначала в общих чертах разобраться как выполняется синхронный код.
Выполнение такого кода движок JavaScript выполняет последовательно (т.е. строчку за строчкой). При этом перед тем, как выполнить какую-то строчку кода интерпретатор сначала помещает её в стек вызовов (call stack). Именно в нём происходит её разбор и исполнение. После этого происходит её извлечение из стека и переход к следующей строчке.
Но всё меняется, когда интерпретатор доходит до выполнения асинхронных операций, например setTimeout
. Они также как и синхронные операции сначала попадают в стек вызовов, где происходит их разбор. Но, при разборе интерпретатор понимает, что это некоторый вызов Web API и помещает эту операцию в него. После этого он удаляет эту строчку из call stack и переходит к выполнению следующей строчки кода.
В это же время Web API регистрирует эту функцию и запускает таймер. Как только он завершается, он помещает эту функцию в очередь (callback queue). Очередь – это структура данных типа FIFO. Она хранит все функции в том порядке, в котором они были туда добавлены.
Очередь обратных вызовов (callback queue) обрабатывает цикл событий (event loop). Он смотрит на эту очередь и на стек вызовов (call stack). Если стек вызовов пуст, а очередь нет – то он берёт первую функцию из очереди и закидывает её в стек вызовов, в котором она уже выполняется. Вот таким образом происходит выполнения асинхронного кода в JavaScript.
Если функцию myFunc
необходимо вызывать не один раз, а постоянно через каждые 3 секунды, то тогда вместо setTimeout
следует использовать setInterval
:
window.setInterval(myFunc, 3000);
Пример, с передачей функции аргументов:
function sayHello(name) {
alert(`Привет, ${name}!`);
}
setTimeout(sayHello, 3000, 'Василий'); // Привет, Василий!
Пример, с использованием в setTimeout
анонимной функции:
setTimeout(function (name) {
alert(`Привет, ${name}!`);
}, 3000, 'Василий');
В setTimeout
и setInterval
можно также вместо функции передавать строку с кодом, который нужно выполнить через определённый интервал времени. Но этот вариант использовать не рекомендуется по тем же причинам, что и функцию eval
.
Если функция setTimeout
по каким-то причинам не работает, то проверьте действительно ли вы передаёте ссылку на функцию, а неё результат:
function sayHello() {
console.log('Привет!');
}
// передаём в setTimeout не ссылку на функцию sayHello, а результат её вызова
setTimeout(sayHello(), 3000);
Отмена таймаута (clearTimeout)
Метод setTimeout
в результате своего вызова возвращает нам некий идентификатор таймера, который затем мы можем использовать для его отмены.
Синтаксис отмены таймаута:
const timeoutId = window.setTimeout(...);
window.clearTimeout(timeoutId);
Данный метод (clearTimeout
) содержит один обязательный параметр - это уникальный идентификатор (timeoutId
) таймера, который можно получить как результат вызова метода setTimeout
.
Например:
// запустим таймер и получим его идентификатор, который будет храниться в timeoutId
const timeoutId = window.setTimeout(() => {
// он выведет alert с контентом 'Сообщение' через 4 секунды
alert('Сообщение');
}, 4000);
// остановим таймер с помощью метода clearTimeout (для этого необходимо в качестве параметра данному методу передать идентификатор таймера, хранящийся в timeoutId)
clearTimeout(timeoutId);

Пример
Например, создадим на странице 2 ссылки. При нажатии на первую будет запускать функцию start
. Данная функция будет выводить в элемент .seconds
количество секунд, прошедших с момента её нажатия. При нажатии на ссылку «Остановить таймер» будем запускать другую функцию stop
. Она будет прекращать работу таймера с помощью метода clearTimeout
.
<div class="seconds-wrapper">Количество секунд: <span class="seconds"></span></div>
<ul>
<li><a href="javascript:start()">Запустить таймер</a></li>
<li><a href="javascript:stop()">Остановить таймер</a></li>
</ul>
<script>
// переменная, для хранения идентификатора таймера
let timeoutId = null;
// функция, которая будет запускать таймер
function start() {
// для предотвращения повторного запуска
if (timeoutId) {
return;
}
// получаем текущее время
const startTime = new Date().getTime();
// функция
const run = () => {
// определяем время, которое прошло с момента запуска функции start
const time = new Date().getTime() - startTime;
// получаем элемент .seconds и выводим в него значение time / 1000
document.querySelector('.seconds').textContent = (time / 1000).toFixed(1);
// запускаем вызов функции run через 50 мс
timeoutId = window.setTimeout(run, 50);
}
// запускаем функцию run
run();
}
// функция для остановки таймера по timeoutId
function stop() {
if (timeoutId) {
// прекращаем работу таймера
clearTimeout(timeoutId);
// присваиваем переменной timeoutId значение null
timeoutId = null;
}
}
</script>

Задача. Перепишите этот пример с использованием методов setInterval
и clearInterval
.
Методы setInterval и clearInterval
Метод setInterval
предназначен для вызова кода на языке JavaScript через указанные промежутки времени. Он в отличие от метода setTimeOut
будет вызвать код до тех пор, пока Вы не остановите этот таймер.
window.setInterval(Параметр_1, Параметр_2);
Метод setInterval имеет два обязательных параметра:
- 1 параметр представляет собой строку, содержащую код на языке JavaScript (например, вызов функции);
- 2 параметр задаёт интервал времени в миллисекундах, через который данный код будет вызываться.
Для прекращения работы данного таймера предназначен метод clearInterval
, которому в качестве параметра необходимо передать уникальный идентификатор (id
) таймера. Этот идентификатор можно получить при установке таймера, т.е. его возвращает метод setInterval
. Также таймер прекращает свою работу при закрытии окна.
//запустим таймер и получим его идентификатор, который будет храниться в переменной timer1
//данный таймер будет выводить сообщение через каждые 5 секунд
var timer1 = window.setInterval("alert('Сообщение');",5000);
//остановим работу таймера с помощью метода clearInterval().
//Для этого в качестве параметра данному методу передадим идентификатор таймера, хранящийся в переменной timer1.
clearInterval(timer1);
Например, создадим цифровые часы:
<p id="clock"></p>
<a href="javaScript:startClock()">3anycтить таймер</a>
<br />
<a href="javaScript:stopClock()">Остановить таймер</a>
<script>
// переменная, для хранения идентификатора таймера
let timeoutId = null;
// для предотвращения повторного запуска
let hasStarting = false;
// функция, которая будет запускать таймер
function start() {
// для предотвращения повторного запуска
if (hasStarting) {
return;
}
hasStarting = true;
// получаем текущее время
const startTime = new Date().getTime();
// функция
const run = () => {
// определяем время, которое прошло с момента запуска функции start
const time = new Date().getTime() - startTime;
// получаем элемент .seconds и выводим в него значение time / 1000
document.querySelector('.seconds').textContent = (time / 1000).toFixed(1);
// запускаем вызов функции run через 50 мс
timeoutId = window.setTimeout(run, 50);
}
// запускаем функцию run
run();
}
// функция для остановки таймера по timeoutId
function stop() {
if (timeoutId) {
// прекращаем работу таймера
clearTimeout(timeoutId);
// присваиваем переменной timeoutId значение null
timeoutId = null;
// присваиваем hasStarting значение false
hasStarting = false;
}
}
</script>

Есть вопросы по примеру из раздела «Отмена таймаута (clearTimeout)».
1.«Счётчик:» не находится внутри тега, так и задумано?
2. //1 — выводит значения переменной count в элемент с id=«clock» (под id=«clock» имеется в виду id=«countTime» ?)
1. Да, он находится в body.
2. Здесь опечатка. Имеется ввиду id=«countTime».
Немного обновил пример и добавил небольшую задачку после него.