Асинхронность в JavaScript. setTimeout и setInterval

Содержание:
  1. setTimeout и setInterval
  2. Отмена таймаута (clearTimeout)
  3. Методы setInterval и clearInterval
  4. Комментарии

На этом уроке мы познакомимся с таймерами, которые предназначены для вызова кода на языке JavaScript через определённые промежутки времени.

setTimeout и setInterval

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

Отличаются они друг от друга лишь тем, что setTimeout выполняет вызов функции всего один раз, а setInterval – постоянно через указанный интервал времени.

Синтаксис setTimeout и setInterval:

JavaScript
// setTimeout
window.setTimeout(func [, delay] [, arg1, arg2, ...]);
// setInterval
window.setTimeout(func [, delay] [, arg1, arg2, ...]);

Параметры:

  • func – функция, которую нужно вызвать спустя указанное в delay количество миллисекунд;
  • delay – количество миллисекунд по истечении которых нужно выполнить func;
  • arg1, arg2, ... – дополнительные аргументы, которые нужно передать в func.

Например, вызовем функцию myFunc один раз по прошествии 3 секунд:

JavaScript
// функция
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:

JavaScript
window.setInterval(myFunc, 3000);

Пример, с передачей функции аргументов:

JavaScript
function sayHello(name) {
  alert(`Привет, ${name}!`);
}
setTimeout(sayHello, 3000, 'Василий'); // Привет, Василий!

Пример, с использованием в setTimeout анонимной функции:

JavaScript
setTimeout(function (name) {
  alert(`Привет, ${name}!`);
}, 3000, 'Василий');

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

Если функция setTimeout по каким-то причинам не работает, то проверьте действительно ли вы передаёте ссылку на функцию, а неё результат:

JavaScript
function sayHello() {
  console.log('Привет!');
}

// передаём в setTimeout не ссылку на функцию sayHello, а результат её вызова
setTimeout(sayHello(), 3000);

Отмена таймаута (clearTimeout)

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

Синтаксис отмены таймаута:

JavaScript
const timeoutId = window.setTimeout(...);
window.clearTimeout(timeoutId);

Данный метод (clearTimeout) содержит один обязательный параметр - это уникальный идентификатор (timeoutId) таймера, который можно получить как результат вызова метода setTimeout.

Например:

JavaScript
// запустим таймер и получим его идентификатор, который будет храниться в timeoutId
const timeoutId = window.setTimeout(() => {
  // он выведет alert с контентом 'Сообщение' через 4 секунды
  alert('Сообщение');
}, 4000);

// остановим таймер с помощью метода clearTimeout (для этого необходимо в качестве параметра данному методу передать идентификатор таймера, хранящийся в timeoutId)
clearTimeout(timeoutId);
Принцип работы с таймерами в JavaScript

Пример

Например, создадим на странице 2 ссылки. При нажатии на первую будет запускать функцию start. Данная функция будет выводить в элемент .seconds количество секунд, прошедших с момента её нажатия. При нажатии на ссылку «Остановить таймер» будем запускать другую функцию stop. Она будет прекращать работу таймера с помощью метода clearTimeout.

HTML
<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>
JavaScript - Использование методов setTimeout и clearTimeout для создания таймера

Задача. Перепишите этот пример с использованием методов setInterval и clearInterval.

Методы setInterval и clearInterval

Метод setInterval предназначен для вызова кода на языке JavaScript через указанные промежутки времени. Он в отличие от метода setTimeOut будет вызвать код до тех пор, пока Вы не остановите этот таймер.

JavaScript
window.setInterval(Параметр_1, Параметр_2);

Метод setInterval имеет два обязательных параметра:

  • 1 параметр представляет собой строку, содержащую код на языке JavaScript (например, вызов функции);
  • 2 параметр задаёт интервал времени в миллисекундах, через который данный код будет вызываться.

Для прекращения работы данного таймера предназначен метод clearInterval, которому в качестве параметра необходимо передать уникальный идентификатор (id) таймера. Этот идентификатор можно получить при установке таймера, т.е. его возвращает метод setInterval. Также таймер прекращает свою работу при закрытии окна.

JavaScript
//запустим таймер и получим его идентификатор, который будет храниться в переменной timer1
//данный таймер будет выводить сообщение через каждые 5 секунд
var timer1 = window.setInterval("alert('Сообщение');",5000);
//остановим работу таймера с помощью метода clearInterval().
//Для этого в качестве параметра данному методу передадим идентификатор таймера, хранящийся в переменной timer1.
clearInterval(timer1);

Например, создадим цифровые часы:

HTML
<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>
Методы JavaScript setInterval и clearInterval

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

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

Немного обновил пример и добавил небольшую задачку после него.
Аноним
Аноним
Спасибо, за доходчивую и понятную статью и наглядный пример!