выберите метод javascript позволяющий выполнять произвольный код через заданные промежутки времени
Асинхронность в JavaScript. setTimeout и setInterval
На этом уроке мы познакомимся с таймерами, которые предназначены для вызова кода на языке JavaScript через определённые промежутки времени.
setTimeout и setInterval
Отличаются они друг от друга лишь тем, что setTimeout выполняет вызов функции всего один раз, а setInterval – постоянно через указанный интервал времени.
Синтаксис setTimeout и setInterval :
Например, вызовем функцию myFunc один раз по прошествии 3 секунд:
Синхронный и асинхронный код
Выполнение такого кода движок JavaScript выполняет последовательно (т.е. строчку за строчкой). При этом перед тем, как выполнить какую-то строчку кода интерпретатор сначала помещает её в стек вызовов ( call stack ). Именно в нём происходит её разбор и исполнение. После этого происходит её извлечение из стека и переход к следующей строчке.
В это же время Web API регистрирует эту функцию и запускает таймер. Как только он завершается, он помещает эту функцию в очередь (callback queue). Очередь – это структура данных типа FIFO. Она хранит все функции в том порядке, в котором они были туда добавлены.
Очередь обратных вызовов (callback queue) обрабатывает цикл событий (event loop). Он смотрит на эту очередь и на стек вызовов (call stack). Если стек вызовов пуст, а очередь нет – то он берёт первую функцию из очереди и закидывает её в стек вызовов, в котором она уже выполняется. Вот таким образом происходит выполнения асинхронного кода в JavaScript.
Если функцию myFunc необходимо вызывать не один раз, а постоянно через каждые 3 секунды, то тогда вместо setTimeout следует использовать setInterval :
Пример, с передачей функции аргументов:
Пример, с использованием в setTimeout анонимной функции:
Если функция setTimeout по каким-то причинам не работает, то проверьте действительно ли вы передаёте ссылку на функцию, а неё результат:
Отмена таймаута (clearTimeout)
Метод setTimeout в результате своего вызова возвращает нам некий идентификатор таймера, который затем мы можем использовать для его отмены.
Синтаксис отмены таймаута:
Пример
Методы setInterval и clearInterval
Метод setInterval предназначен для вызова кода на языке JavaScript через указанные промежутки времени. Он в отличие от метода setTimeOut будет вызвать код до тех пор, пока Вы не остановите этот таймер.
Метод setInterval имеет два обязательных параметра:
Работа с таймерами в JavaScript
Учебник JavaScript
Практика
Работа с DOM
Практика
Некоторые продвинутые вещи
Рекомендованное ES6
Некоторые видео могут забегать вперед, тк к этому месту учебника мы прошли еще не весь ES6. Просто пропускайте такие видео, посмотрите потом.
Регулярки
Разное
Работа с канвасом
Практика
Контекст
Drag-and-Drop
Практика по ООП
Ваша задача: посмотрите, попробуйте повторить.
Практика
Promise ES6
Библиотека jQuery
Тк. jQuery устаревает, объявляю эти уроки не обязательными и выношу в конец учебника (так по уровню уроки середины учебника, если что). В перспективе переедет в отдельный учебник по jq.
В данном уроке мы научимся работать с таймерами в JavaScript. Разобравшись с таймерами, вы сможете автоматически выполнять на странице какие-либо операции через заданный промежуток времени. К примеру, можно будет сделать слайдер картинок, в котором картинки будут меняться каждую секунду.
Для работы с таймерами в JavaScript используется метод setInterval, который запускает заданный код через определенные промежутки времени.
Метод setInterval
Давайте рассмотрим нужный для этого код. Пусть у нас дан инпут. Будем каждую секунду увеличивать значение его атрибута value на единицу:
Это нужно, так как атрибут всегда отдает строку, даже если там хранится число, как у нас, то есть elem.value вернет ‘1’, а не 1 (в самом начале таймера, когда в атрибуте еще 1). И получится, что elem.value + 1 это ‘1’+1, что дает ’11’, а не 2).
Если же написать parseInt, то в инпуте будет сначала 1, потом 2, потом 3 и так далее.
Остановка таймера
Вы уже знаете, как запустить таймер, давайте теперь научимся его останавливать. Для этого используется метод clearInterval, который принимает уникальный номер того таймера (созданного через setInterval), который нужно остановить.
Давайте посмотрим откуда берется этот номер:
То есть при создание таймера через setInterval мы можем узнать его номер, и потом передать его методу clearInterval, чтобы таймер остановился.
Метод setTimeout
Следующий метод, который нам нужен, называется setTimeout. Он позволяет сделать задержку перед запуском кода (эта задержка случится только один раз и код выполнится только один раз, в отличии от setInterval).
Планирование: setTimeout и setInterval
Мы можем вызвать функцию не в данный момент, а позже, через заданный интервал времени. Это называется «планирование вызова».
Для этого существуют два метода:
Эти методы не являются частью спецификации JavaScript. Но большинство сред выполнения JS-кода имеют внутренний планировщик и предоставляют доступ к этим методам. В частности, они поддерживаются во всех браузерах и Node.js.
setTimeout
Например, данный код вызывает sayHi() спустя одну секунду:
Если первый аргумент является строкой, то JavaScript создаст из неё функцию.
Это также будет работать:
Но использование строк не рекомендуется. Вместо этого используйте функции. Например, так:
Начинающие разработчики иногда ошибаются, добавляя скобки () после функции:
Отмена через clearTimeout
Синтаксис для отмены:
В коде ниже планируем вызов функции и затем отменяем его (просто передумали). В результате ничего не происходит:
Повторюсь, что нет единой спецификации на эти методы, поэтому такое поведение является нормальным.
Для браузеров таймеры описаны в разделе таймеров стандарта HTML5.
setInterval
Метод setInterval имеет такой же синтаксис как setTimeout :
Все аргументы имеют такое же значение. Но отличие этого метода от setTimeout в том, что функция запускается не один раз, а периодически через указанный интервал времени.
Следующий пример выводит сообщение каждые 2 секунды. Через 5 секунд вывод прекращается:
Так что если вы запустите код выше и подождёте с закрытием alert несколько секунд, то следующий alert будет показан сразу, как только вы закроете предыдущий. Интервал времени между сообщениями alert будет короче, чем 2 секунды.
Рекурсивный setTimeout
Есть два способа запускать что-то регулярно.
Например, необходимо написать сервис, который отправляет запрос для получения данных на сервер каждые 5 секунд, но если сервер перегружен, то необходимо увеличить интервал запросов до 10, 20, 40 секунд… Вот псевдокод:
А если функции, которые мы планируем, ресурсоёмкие и требуют времени, то мы можем измерить время, затраченное на выполнение, и спланировать следующий вызов раньше или позже.
Сравним два фрагмента кода. Первый использует setInterval :
Второй использует рекурсивный setTimeout :
Для setInterval внутренний планировщик будет выполнять func(i) каждые 100 мс:
Реальная задержка между вызовами func с помощью setInterval меньше, чем указано в коде!
Вполне возможно, что выполнение func будет дольше, чем мы ожидали, и займёт более 100 мс.
В данном случае движок ждёт окончания выполнения func и затем проверяет планировщик и, если время истекло, немедленно запускает его снова.
Ниже представлено изображение, показывающее процесс работы рекурсивного setTimeout :
Рекурсивный setTimeout гарантирует фиксированную задержку (здесь 100 мс).
Это потому, что новый вызов планируется в конце предыдущего.
Есть и побочный эффект. Функция ссылается на внешнее лексическое окружение, поэтому пока она существует, внешние переменные существуют тоже. Они могут занимать больше памяти, чем сама функция. Поэтому, если регулярный вызов функции больше не нужен, то лучше отменить его, даже если функция очень маленькая.
setTimeout с нулевой задержкой
Это планирует вызов func настолько быстро, насколько это возможно. Но планировщик будет вызывать функцию только после завершения выполнения текущего кода.
Так вызов функции будет запланирован сразу после выполнения текущего кода.
Например, этот код выводит «Привет» и затем сразу «Мир»:
Первая строка помещает вызов в «календарь» через 0 мс. Но планировщик проверит «календарь» только после того, как текущий код завершится. Поэтому «Привет» выводится первым, а «Мир» – после него.
Есть и более продвинутые случаи использования нулевой задержки в браузерах, которые мы рассмотрим в главе Событийный цикл: микрозадачи и макрозадачи.
В браузере есть ограничение на то, как часто внутренние счётчики могут выполняться. В стандарте HTML5 говорится: «после пяти вложенных таймеров интервал должен составлять не менее четырёх миллисекунд.».
Аналогичное происходит при использовании setInterval вместо setTimeout : setInterval(f) запускает f несколько раз с нулевой задержкой, а затем с задержкой 4+ мс.
Это ограничение существует давно, многие скрипты полагаются на него, поэтому оно сохраняется по историческим причинам.
Этого ограничения нет в серверном JavaScript. Там есть и другие способы планирования асинхронных задач. Например, setImmediate для Node.js. Так что это ограничение относится только к браузерам.
Итого
Обратим внимание, что все методы планирования не гарантируют точную задержку.
Например, таймер в браузере может замедляться по многим причинам:
Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС.
Задачи
Вывод каждую секунду
Сделайте два варианта решения.
Функции setTimeout, setInterval и clearInterval
Рассмотрим две часто используемые в JavaScript функции: setTimeout и setInterval. Первая позволяет выполнять произвольную функцию через определенный период времени и останавливается, а вторая – многократно выполняет функцию через указанный интервал.
Функция setTimeout имеет такой синтаксис:
Например, запустим функцию
Обратите внимание, мы передаем в setTimeout ссылку на функцию, поэтому круглые скобки после ее имени не пишем. Далее указан интервал запуска 2000 мс = 2 сек.
Если функции нужно передать какие-либо аргументы, то это делается так:
Во всех примерах функция запускалась только один раз спустя 2 сек. Поэтому, setTimeout в таких случаях можно не останавливать – она автоматически завершится после однократного запуска функции. Но, бывают случаи, когда setTimeout нужно завершить до ее запуска. Например, с сервера загружаются сообщения: они могут скачаться быстро, если составляют небольшой размер, но могут скачиваться долго, в случае передачи фотографии или видео. Скрипт на стороне клиента не может заранее знать об объеме принимаемых данных, поэтому для подстраховки запускается отложенный вызов функции, которая на экране отображает анимированное изображение, показывающее процесс загрузки. Почему вызов отложенный? Потому что при быстрой загрузке ничего показывать не нужно, пользователь этого не заметит. И в этом случае нужно прервать функцию setTimeout, чтобы на экране ничего не отображалось. Это может выглядеть так:
В setTimeout также можно передавать строки с выражением, которое нужно выполнить:
Но вместо них лучше использовать функции, в том числе и стрелочные:
Если в setTimeout не указывается второй аргумент, то задержка принимается равной 0 и функция, переданная в setTimeout запускается настолько быстро, насколько это возможно. Но планировщик будет вызывать функцию только после завершения выполнения текущего кода. Например:
Увидим сообщения: «Привет» и «Мир».
Функция setInterval
Вторая функция setInterval имеет такой же синтаксис, что и функция setTimeout, но выполняет переданную ей функцию многократно с заданным интервалом, пока не будет остановлена:
В этом примере мы, используя замыкание, создали функцию clock и далее, запускаем ее с интервалом в 1 секунду. Она будет выполняться до тех пор, пока мы ее не остановим:
Остановку делаем с помощью отложенного вызова анонимной функции через 5 секунд, в которой вызываем clearInterval с указанием idClock.
Во всех рассмотренных примерах на передаваемые функции в setTimeout или setInterval автоматически создавались ссылки и хранились в планировщике. Это предотвращало удалений функций сборщиком мусора, даже если на них не было явных внешних ссылок в скрипте. Например:
На стрелочную функцию ссылается планировщик до момента ее выполнения. А в
анонимная функция остается в памяти до тех пор, пока не будет вызван
Здесь стоит иметь в виду, что передаваемая функция ссылается на внешнее лексическое окружение, поэтому пока она существует, внешние переменные тоже существуют. Они могут занимать больше памяти, чем сама функция. Поэтому, если регулярный вызов функции больше не нужен, то лучше отменить его.
Потеря this
Одной из проблем использования setTimeout и setInterval является потеря this при вызове методов объектов. Например:
В консоле мы увидим undefined. Почему? Дело в том, что здесь теряется контекст при вызове функции. Это эквивалентно вот такому вызову:
И, так как в JavaScript this вычисляется динамически при каждом вызове функции, то здесь JavaScript-машина просто не может связать функцию show с объектом car.
Исправить ситуацию можно несколькими способами. Первый:
вызвать car.showModel через анонимную функцию-обертку. Здесь мы при вызове явно указываем объект car, поэтому this будет определен корректно. Второй способ – использовать метод bind, о котором мы говорили на предыдущем занятии:
Этот способ предпочтительнее использовать на практике, так как после вызова bind он не зависит от значения переменной car. Если она будет изменена, то функция show все равно корректно вызовется, а вот в первом случае мы бы получили ошибку. Вот это следует иметь в виду при реализации отложенных вызовов.
И в заключение занятия пару слов об особенностях работы стрелочных функций. Важно знать, что у стрелочных функций нет своего контекста выполнения (лексического окружения), а значит, нет и своего this. В некоторых случаях этот момент имеет ключевое значение, например:
Здесь метод forEach при вызове обычной функции устанавливает контекст this=undefined, но при использовании стрелочной функции контекст неизбежно берется от функции showList и this=group. Например, если заменить стрелочную функцию на обычную, то получим ошибку:
с сообщением undefined не имеет свойства title.
Видео по теме
JavaScipt #1: что это такое, с чего начать, как внедрять и запускать
JavaScipt #2: способы объявления переменных и констант в стандарте ES6+
JavaScript #3: примитивные типы number, string, Infinity, NaN, boolean, null, undefined, Symbol
JavaScript #4: приведение типов, оператор присваивания, функции alert, prompt, confirm
JavaScript #6: условные операторы if и switch, сравнение строк, строгое сравнение
JavaScript #7: операторы циклов for, while, do while, операторы break и continue
JavaScript #8: объявление функций по Function Declaration, аргументы по умолчанию
JavaScript #9: функции по Function Expression, анонимные функции, callback-функции
JavaScript #10: анонимные и стрелочные функции, функциональное выражение
JavaScript #11: объекты, цикл for in
JavaScript #12: методы объектов, ключевое слово this
JavaScript #13: клонирование объектов, функции конструкторы
JavaScript #14: массивы (array), методы push, pop, shift, unshift, многомерные массивы
JavaScript #15: методы массивов: splice, slice, indexOf, find, filter, forEach, sort, split, join
JavaScript #16: числовые методы toString, floor, ceil, round, random, parseInt и другие
JavaScript #18: коллекции Map и Set
JavaScript #19: деструктурирующее присваивание
JavaScript #20: рекурсивные функции, остаточные аргументы, оператор расширения
JavaScript #21: замыкания, лексическое окружение, вложенные функции
JavaScript #22: свойства name, length и методы call, apply, bind функций
JavaScript #23: создание функций (new Function), функции setTimeout, setInterval и clearInterval
© 2021 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта
Планирование в JavaScript: как применять функции setTimeout и setInterval
Метод setTimeout — это встроенная функция JavaScript, устанавливающая таймер обратного отсчета (в миллисекундах) для выполнения функции обратного вызова по завершении заданного времени.
Представьте, что у вас появилась задача сделать всплывающее окно с приветствием, отображаемое через две секунды после того, как пользователь зашел на сайт. Или же необходимо воспроизвести звук через секунду после объявления победителя в онлайн-игре. В такой ситуации вам не нужно, чтобы функция запускалась немедленно. Надо отложить ее выполнение или запустить через определенный интервал времени.
Метод setTimeout
Метод setTimeout запускается только один раз за вызов, а значит после завершения выполнения функции setTimeout прекращает свою работу.
Основной и наиболее часто используемый синтаксис этой функции:
setTimeout (функция обратного вызова, задержка в миллисекундах)
В этом примере все просто: функция обратного вызова сработает по завершении временной задержки. Давайте рассмотрим пример, как использовать эту функцию на практике:
setTimeout () позволяет нам назначить столько аргументов, сколько необходимо для функции обратного вызова. Предположим, что мы хотим поприветствовать Джона — пользователя, вошедшего в нашу систему. Для этого нам необходимо просто добавить аргументы в конец списка параметров setTimeout :
setTimeout (функция обратного вызова, задержка в миллисекундах, arg1)
Посмотрим, как это реализовать:
Глядя на код предыдущего примера у вас может возникнуть соблазн передать аргументы в качестве параметров функции обратного вызова. Например:
SetTimeout (Greet(loggedInUser), 2000);
Функция clearTimeout
Иногда бывают ситуации, когда нам нужно отменить начавшуюся временную задержку. Что делать в этом случае? Метод setTimeout () возвращает ключ, позволяющий ссылаться на выполнение callback-функции. Мы можем использовать этот ключ как кнопку прерывания, тем самым взяв в свои руки контроль над самим механизмом тайм-аута. Например:
const timerId = setTimeout(Greet, 5000, loggedInUser);
Здесь timerId — тот самый ключ, используемый для ссылки на текущий таймер. Чтобы отменить тайм-аут, этот ключ нужно передать функции clearTimeout () в качестве параметра:
При использовании clearTimeout() происходит отмена таймера, и функция обратного вызова внутри метода не сработает.
Метод setInterval
Метод setInterval используется для повторного выполнения функции по истечении определенного периода времени. Схема построения этого метода следующая:
В этом случае задержка — это время в миллисекундах, в течение которого таймер должен задерживать последовательные выполнения функции.
В примере метод будет повторно выводить в консоль Hello John каждые три секунды.
Вернемся к нашему примеру:
Функция sayHello будет выполнена всего три раза.
Вы можете задаться вопросом, почему оператор if находится внутри функции, а не за ее пределами, вот так:
Для того чтобы ответить на него, необходимо разобраться, как JavaScript выполняет рассматриваемые нами методы. В отличие от других языков, в JS — один поток для решения задач, выполняемый построчно. Это значит, что каждая строка кода должна завершить свое выполнение, прежде чем переходить к следующей. Другими словами, выполнение остального кода блокируется.
Но есть операции ввода-вывода, которые не блокируются. Они обрабатываются базовым механизмом. К ним относятся:
Следовательно, если бы мы прописали реализацию метода вторым способом, таймер не перестал бы работать, ведь после выполнения строки let timerID = setInterval(sayHello, 3000, «John») JavaScript перешел бы к следующему блоку кода if (counter === 3)
Рекурсивный setTimeout
Метод setInterval запускает функцию каждые n миллисекунд, не обращая внимания на то, когда она завершится.
Если функция каждый раз срабатывает с одним и тем же временным интервалом, тогда все в порядке:
Функция срабатывает с одним и тем же временным интервалом
Но бывают случаи, когда время выполнения меняется в зависимости от условий сети, например:
Время выполнения может меняться
Или перекрывает следующее:
Время выполнения перекрывает следующее за ним
Чтобы избежать последствий подобных случаев, вы можете запланировать рекурсивный вызов setTimeout по завершении функции обратного вызова.
Для наглядности рассмотрим распространенный пример сервиса по отправке запросов на сервер, который при перегрузке последнего увеличивал бы интервал следующего запроса:
C рекурсивным setTimeout мы можем изменять последующий вызов, исходя из результата предыдущего.
Фиксированная временная задержка
Другими словами, такой подход дает фиксированную временную задержку, ведь новый вызов планируется, когда закончился вызов перед ним.
Что надо помнить
Но здесь есть свои подводные камни. Функция обратного вызова все равно будет взаимодействовать с внешними переменными, иногда занимающими больше памяти, чем она сама. В таком случае при отсутствии большой необходимости в частом вызове функции есть смысл его отменить.
Нулевая задержка
В официальной документации к setTimeout написано, что delay (временная задержка) — необязательное поле. Если оно не указано, то таймер равен нулю и функция обратного вызова срабатывает «мгновенно или как можно скорее». Такая странная формулировка обусловлена наличием у браузера своего таймера с минимально возможной задержкой. Она может варьироваться от 4 мс в современных браузерах, до 15 мс в более старых. Но срабатывать она может и реже — с опозданием вплоть до 1000 мс.
Это связано с особенностью некоторых браузеров запускать таймер на неактивной странице. Этим грешат Google Chrome, Firefox, IE10. На частоту запуска функции также влияют загрузка процессора и режим экономии энергии разряженной батареи при работе на ноутбуке.
Пример использования метода
Рассмотрим простой пример реализации всплывающего сообщения, появляющегося спустя 2000 миллисекунд после наведения мышкой на элемент. Условие: если курсор убрали раньше, сообщение не нужно показывать.
Резюмируем
Простой метод измерения реальной скорости загрузки страниц у посетителей сайта
Как работает Server-Sent API с примерами
Как можно закэшировать данные и выиграть в производительности
Примеры применения Javascript в Nginx’e
В своем блоге индийский разработчик Шашват Верма (Shashwat Verma) рассказал, как преобразовать веб-сайт или веб-страницу в прогрессивное веб-приложение (PWA).
Как просто сделать удобный дебаг и не лазить в код или как бородатые хакеры перехватывают ajax-запросы, нарушая вашу безопасность.