Задержка setTimeout не работает
Я пытаюсь обернуть голову вокруг setTimeout , но не могу заставить ее работать должным образом.
Я хочу, чтобы текст просчитывался после щелчка якоря — но мой setTimeout , кажется, срабатывает в то же время, хотя я установил задержку на 1 сек.
Любой намек на то, что я могу делать неправильно, высоко ценится 🙂
7 ответов
setTimeout принимает функцию в качестве аргумента. Вы выполняете функцию и передаете результат в setTimeout (поэтому функция выполняется сразу же). Вы можете использовать анонимные функции, например:
Обратите внимание, что то же самое верно для setInterval .
Существует условие для передачи аргументов функции. В вашем случае вы можете сделать это
Третий аргумент функции setTimeout будет передан в функцию writeNumber.html
Вам нужно использовать функции, которые будут вызываться после истечения времени ожидания; Вы также можете использовать анонимную функцию, тогда ваша функция foo будет выглядеть так:
Я приземлился на этот вопрос. На него ответили адекватно, и я думаю, что использование setInterval , как предложил @Purag, вероятно, является наилучшим подходом для получения желаемого функционального поведения. Однако в первоначальном примере кода не учитывалось асинхронное поведение JavaScript. Это часто встречающаяся ошибка, которую я сделал сам более чем случайно :).
В качестве дополнительного примечания я хотел дать другое возможное решение для этого, которое имитирует первоначальную попытку, но на этот раз рассматривает асинхронность Javascript:
Конечно же, это явно ужасный код!
Я дал рабочий JSFiddle этого в мой собственный вопрос. Этот код иллюстрирует так называемую пирамиду гибели . И это может быть смягчено с помощью обещаний JavaScript, как показано в ответах на мой вопрос. Требуется некоторая работа, чтобы написать версию WriteNumber() , которая использует Promises, но затем код может быть переписан так:
Вам нужно использовать функцию reference , которая будет вызвана позже, когда истечет время таймера. Оберните каждый оператор в анонимную функцию, чтобы он выполнялся не сразу, а по истечении таймера.
Кроме того, вы хотите использовать разные значения задержки для каждого, чтобы таймеры не истекали в одно и то же время. Смотрите обновленную скрипку на http://jsfiddle.net/RqCqM/
Вы должны обернуть свои заявления в анонимные функции, а также поразить ваши сроки —
Если вы установите все в 1000 , шаги будут в значительной степени выполняться одновременно, так как функция setTimeout будет запускать задачу через 1 секунду после того, как вы вызвали функцию, а не через 1 секунду после предыдущего вызова setTimeout функция закончена.
Просто используйте setInterval() . Вот, что я и придумал. Вот ваш новый JavaScript:
Источник
Планирование: 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 .
Сделайте два варианта решения.
- Используя setInterval .
- Используя рекурсивный setTimeout .
Источник
Вызовите setTimeout без задержки
Довольно часто в библиотеках JavaScript код выглядит так:
Я хотел бы знать, почему использовать такой код оболочки.
5 ответов
Браузеры являются однопоточными, и этот единственный поток (поток пользовательского интерфейса) используется совместно механизмом рендеринга и механизмом js.
Если то, что вы хотите сделать, занимает много времени (мы говорим здесь циклы, но все же), это может остановить (приостановить) рендеринг (поток и рисование).
В браузерах также существует «корзина», в которой все события сначала помещаются в ожидание, пока поток пользовательского интерфейса не выполнит то, что он делает. Как только поток завершен, он смотрит в корзину и выбирает задачу первым в очереди.
Используя setTimeout , вы создаете новую задачу в корзине после задержки и позволяете потоку справиться с ней, как только она станет доступной для дальнейшей работы.
После задержки 0 мс создайте новое задание функции и поместите его в корзину. В этот момент поток пользовательского интерфейса занят чем-то другим, и в корзине уже есть другие задачи. Через 6 мс поток становится доступным и получает задание перед вами, хорошо, вы следующий. Но что? Это была одна огромная вещь! Это было как раньше (30 мс)!
Наконец, теперь поток завершен, приходит и получает вашу задачу.
У большинства браузеров минимальная задержка больше 0, поэтому установка 0 в качестве задержки означает: поместите эту задачу в корзину как можно скорее. Но указание UA положить его в ведро КАК МОЖНО СКОРЕЕ, это не гарантия, что он будет выполнен в тот момент. Ведро похоже на почтовое отделение, может быть, есть длинная очередь других задач. Почтовые отделения также являются однопоточными, и только один человек помогает выполнять все задачи . извините клиентов за их задачи. Ваша задача должна встать в очередь, как и все остальные.
Если браузер не реализует свой собственный тикер, он использует циклы тиков ОС. Старые браузеры имели минимальные задержки между 10-15 мс. HTML5 указывает, что если задержка меньше затем 4 мс UA должен увеличить его до 4 мс. Говорят, что это согласовано во всех браузерах, выпущенных в 2010 году и далее.
См. Как работают таймеры JavaScript Джона Ресига для получения дополнительной информации.
Если вы хотите выполнить оставшуюся часть кода, не дожидаясь завершения предыдущего, вам нужно добавить его анонимным методом, переданным в функцию setTimeout. В противном случае ваш код будет ждать до завершения предыдущего
Пример:
Вот почему я использую это.
Есть несколько причин, почему вы бы это сделали
- Есть действие, которое вы не хотите запускать немедленно, но хотите запустить в ближайшем будущем.
- Вы хотите разрешить запуск других ранее зарегистрированных обработчиков из setTimeout или setInterval
Помимо предыдущих ответов, я хотел бы добавить еще один полезный сценарий, о котором я могу подумать: «убежать» из блока try-catch. Задержка setTimeout из блока try-catch будет выполнена вне блока, и любое исключение будет распространяться в глобальной области видимости.
Возможно, лучший пример сценария: в сегодняшнем JavaScript, с более распространенным использованием так называемых отложенных / обещаний для асинхронных обратных вызовов, вы (часто) фактически выполняете внутри try-catch. Отложенные / обещания обертывают обратный вызов в try-catch, чтобы иметь возможность обнаруживать и распространять исключение как ошибку в асинхронной цепочке. Это все хорошо для функций, которые должны быть в цепочке, но рано или поздно вы «сделали» (т.е. извлекли весь ваш ajax) и хотите запустить простой неасинхронный код, где вы не хотите, чтобы исключения были » скрыто AFAIK Dojo, Kris Kowal’s Q, MochiKit и Google Closure lib используют упаковку try-catch (но не jQuery).
(В нескольких странных случаях я также использовал технику для перезапуска кода в одноэлементном стиле без рекурсии. Т.е. выполняю разрыв-перезапуск в том же цикле).
Чтобы разрешить выполнение любых ранее установленных таймаутов.
Источник