Как правильно вывести приветствие через 5 секунд после запуска скрипта
Перейти к содержимому

Как правильно вывести приветствие через 5 секунд после запуска скрипта

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

Александр Мальцев

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

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

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

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

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

При выполнение этого кода программа не остановится на месте, где мы зарегистрировали некоторую асинхронность посредством 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 :

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

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

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

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

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

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

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

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

Принцип работы с таймерами в JavaScript

Пример

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

JavaScript - Использование методов setTimeout и clearTimeout для создания таймера

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

Методы setInterval и clearInterval

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

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

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

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

BOM — События по таймеру

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

Это называется событием по таймеру.

События по таймеру

Объект window позволяет выполнять код через заранее определенный период времени или временные интервалы.

Эти временные интервалы называются событиями по таймеру.

Два ключевых метода, которые позволяют это реализовать:

  • setTimeout(функция, миллисекунды) — Выполняет функцию по истечению заданного числа миллисекунд.
  • setInterval(функция, миллисекунды) — Аналогичен методу setTimeout(), но при этом вызов функции постоянно повторяется.

Оба метода — setTimeout() и setInterval() — являются методами объекта window.

Метод setTimeout()

window.setTimeout(функция, миллисекунды);

Метод window.setTimeout() может записываться без префикса window.

Первый параметр — это вызываемая функция.

Второй параметр задает время в миллисекундах, которое должно пройти до вызова функции.

В следующем примере после нажатия на кнопку через 3 секунды появляется всплывающее окно с сообщением «Привет»:

Метод clearTimeout()

Метод clearTimeout() останавливает выполнение функции, вызванной в методе setTimeout().

window.clearTimeout(timeoutVariable);

Метод window.clearTimeout() может записываться без префикса window.

Метод clearTimeout() использует переменную, возвращаемую методом setTimeout():

myVar = setTimeout(функция, миллисекунды);
clearTimeout(myVar);

Если функция, срабатывающая по таймеру, еще не была вызвана, то ее выполнение можно остановить вызвав метод clearTimeout().

Следующий пример аналогичен предыдущему, только мы добавили кнопку «Остановить»:

Метод setInterval()

Метод setInterval() регулярно повторяет вызов функции через заданный интервал времени.

window.setInterval(функция, миллисекунды);

Метод window.setInterval() может записываться без префикса window.

Первый параметр — это вызываемая функция.

Второй параметр задает время в миллисекундах, которое должно пройти между вызовами функции.

В следующем примере вызывается функция myTimer, которая выводит текущее время, каждую секунду (как в цифровых часах):

Внимание! В 1 секунде — 1000 миллисекунд.

Метод clearInterval()

Метод clearInterval() останавливает выполнение функции, вызываемой методом setInterval().

window.clearInterval(timerVariable);

Метод window.clearInterval() может записываться без префикса window.

Метод clearInterval() использует переменную, возвращаемую методом setInterval():

myVar = setInterval(функция, миллисекунды); clearInterval(myVar);

Следующий пример аналогичен предыдущему, только мы добавили кнопку «Остановить время»:

Планирование: setTimeout и setInterval

Мы можем вызвать функцию не в данный момент, а позже, через заданный интервал времени. Это называется «планирование вызова».

Для этого существуют два метода:

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

Эти методы не являются частью спецификации JavaScript. Но большинство сред выполнения JS-кода имеют внутренний планировщик и предоставляют доступ к этим методам. В частности, они поддерживаются во всех браузерах и Node.js.

setTimeout

func|code Функция или строка кода для выполнения. Обычно это функция. По историческим причинам можно передать и строку кода, но это не рекомендуется. delay Задержка перед запуском в миллисекундах (1000 мс = 1 с). Значение по умолчанию – 0. arg1 , arg2 … Аргументы, передаваемые в функцию (не поддерживается в IE9-)

Например, данный код вызывает sayHi() спустя одну секунду:

Если первый аргумент является строкой, то JavaScript создаст из неё функцию.

Это также будет работать:

Но использование строк не рекомендуется. Вместо этого используйте функции. Например, так:

Начинающие разработчики иногда ошибаются, добавляя скобки () после функции:

Это не работает, потому что setTimeout ожидает ссылку на функцию. Здесь sayHi() запускает выполнение функции, и результат выполнения отправляется в setTimeout . В нашем случае результатом выполнения sayHi() является undefined (так как функция ничего не возвращает), поэтому ничего не планируется.

Отмена через clearTimeout

Вызов setTimeout возвращает «идентификатор таймера» timerId , который можно использовать для отмены дальнейшего выполнения.

Синтаксис для отмены:

В коде ниже планируем вызов функции и затем отменяем его (просто передумали). В результате ничего не происходит:

Как мы видим из вывода alert , в браузере идентификатором таймера является число. В других средах это может быть что-то ещё. Например, Node.js возвращает объект таймера с дополнительными методами.

Повторюсь, что нет единой спецификации на эти методы, поэтому такое поведение является нормальным.

Для браузеров таймеры описаны в разделе таймеров стандарта HTML5.

setInterval

Метод setInterval имеет такой же синтаксис как setTimeout :

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

Чтобы остановить дальнейшее выполнение функции, необходимо вызвать clearInterval(timerId) .

Следующий пример выводит сообщение каждые 2 секунды. Через 5 секунд вывод прекращается:

В большинстве браузеров, включая Chrome и Firefox, внутренний счётчик продолжает тикать во время показа alert/confirm/prompt .

Так что если вы запустите код выше и подождёте с закрытием alert несколько секунд, то следующий alert будет показан сразу, как только вы закроете предыдущий. Интервал времени между сообщениями alert будет короче, чем 2 секунды.

Вложенный setTimeout

Есть два способа запускать что-то регулярно.

Один из них setInterval . Другим является вложенный setTimeout . Например:

Метод setTimeout выше планирует следующий вызов прямо после окончания текущего (*) .

Вложенный setTimeout – более гибкий метод, чем setInterval . С его помощью последующий вызов может быть задан по-разному в зависимости от результатов предыдущего.

Например, необходимо написать сервис, который отправляет запрос для получения данных на сервер каждые 5 секунд, но если сервер перегружен, то необходимо увеличить интервал запросов до 10, 20, 40 секунд… Вот псевдокод:

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

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

Сравним два фрагмента кода. Первый использует setInterval :

Второй использует вложенный setTimeout :

Для setInterval внутренний планировщик будет выполнять func(i) каждые 100 мс:

Реальная задержка между вызовами func с помощью setInterval меньше, чем указано в коде!

Это нормально, потому что время, затраченное на выполнение func , использует часть заданного интервала времени.

Вполне возможно, что выполнение func будет дольше, чем мы ожидали, и займёт более 100 мс.

В данном случае движок ждёт окончания выполнения func и затем проверяет планировщик и, если время истекло, немедленно запускает его снова.

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

Ниже представлено изображение, показывающее процесс работы рекурсивного setTimeout :

Вложенный setTimeout гарантирует фиксированную задержку (здесь 100 мс).

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

Когда функция передаётся в setInterval/setTimeout , на неё создаётся внутренняя ссылка и сохраняется в планировщике. Это предотвращает попадание функции в сборщик мусора, даже если на неё нет других ссылок.

Для setInterval функция остаётся в памяти до тех пор, пока не будет вызван clearInterval .

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

setTimeout с нулевой задержкой

Особый вариант использования: setTimeout(func, 0) или просто setTimeout(func) .

Это планирует вызов func настолько быстро, насколько это возможно. Но планировщик будет вызывать функцию только после завершения выполнения текущего кода.

Так вызов функции будет запланирован сразу после выполнения текущего кода.

Например, этот код выводит «Привет» и затем сразу «Мир»:

Первая строка помещает вызов в «календарь» через 0 мс. Но планировщик проверит «календарь» только после того, как текущий код завершится. Поэтому "Привет" выводится первым, а "Мир" – после него.

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

В браузере есть ограничение на то, как часто внутренние счётчики могут выполняться. В стандарте HTML5 говорится: «после пяти вложенных таймеров интервал должен составлять не менее четырёх миллисекунд.».

Продемонстрируем в примере ниже, что это означает. Вызов setTimeout повторно вызывает себя через 0 мс. Каждый вызов запоминает реальное время от предыдущего вызова в массиве times . Какова реальная задержка? Посмотрим:

Первый таймер запускается сразу (как и указано в спецификации), а затем задержка вступает в игру, и мы видим 9, 15, 20, 24. .

Аналогичное происходит при использовании setInterval вместо setTimeout : setInterval(f) запускает f несколько раз с нулевой задержкой, а затем с задержкой 4+ мс.

Это ограничение существует давно, многие скрипты полагаются на него, поэтому оно сохраняется по историческим причинам.

Этого ограничения нет в серверном JavaScript. Там есть и другие способы планирования асинхронных задач. Например, setImmediate для Node.js. Так что это ограничение относится только к браузерам.

Итого

  • Методы setInterval(func, delay, . args) и setTimeout(func, delay, . args) позволяют выполнять func регулярно или только один раз после задержки delay , заданной в мс.
  • Для отмены выполнения необходимо вызвать clearInterval/clearTimeout со значением, которое возвращают методы setInterval/setTimeout .
  • Вложенный вызов setTimeout является более гибкой альтернативой setInterval . Также он позволяет более точно задать интервал между выполнениями.
  • Планирование с нулевой задержкой setTimeout(func,0) или, что то же самое, setTimeout(func) используется для вызовов, которые должны быть исполнены как можно скорее, после завершения исполнения текущего кода.
  • Браузер ограничивает 4-мя мс минимальную задержку между пятью и более вложенными вызовами setTimeout , а также для setInterval , начиная с 5-го вызова.

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

Например, таймер в браузере может замедляться по многим причинам:

  • Перегружен процессор.
  • Вкладка браузера в фоновом режиме.
  • Работа ноутбука от аккумулятора.

Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС.

Задачи

Вывод каждую секунду

Напишите функцию printNumbers(from, to) , которая выводит число каждую секунду, начиная от from и заканчивая to .

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

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