js как сделать задержку в коде
Объединённый асинхронный JavaScript: Таймауты и интервалы
Необходимые условия: | Базовая компьютерная грамотность, достаточное понимание основ JavaScript. |
---|---|
Цель: | Понимание асинхронных циклов и интервалов, и то как их можно использовать. |
Введение
В течение долгого времени веб-платформа предлагала программистам JavaScript ряд функций, которые позволяли им асинхронно выполнять код по истечении определённого временного интервала и повторно выполнять асинхронный блок кода, пока вы не скажете ему остановиться.
setTimeout() Выполняет указанный блок кода один раз по истечении указанного времени setInterval() Выполняет указанный блок кода несколько раз с определённым интервалом между каждым вызовом. requestAnimationFrame() Современная версия setInterval (). Выполняют указанный блок кода перед тем, как браузер в следующий раз перерисовывает отображение, позволяя запускать анимацию с подходящей частотой кадров независимо от среды, в которой она выполняется.
Асинхронный код, установленный этими функциями, выполняется в основном потоке (по истечении указанного им таймера).
Важно знать, что вы можете (и часто будете) запускать другой код до выполнения вызова setTimeout () или между итерациями setInterval (). В зависимости от того, насколько интенсивно используются эти операции для процессора, они могут ещё больше задержать выполнение асинхронного кода, поскольку любой асинхронный код будет выполняться только после того, как станет доступен основной поток. (Другими словами, когда стек пуст.) вы узнаете больше по этому вопросу по мере изучения этой статьи.
В любом случае эти функции используются для запуска постоянной анимации и другой фоновой обработки на веб-сайте или в приложении. В следующих разделах мы покажем вам, как их можно использовать.
setTimeout()
Как мы ранее отметили, setTimeout () выполняет определённый блок кода один раз по истечении заданного времени. Принимает следующие параметры:
NOTE: Указанное время (или задержка) не является гарантированным временем выполнения, а скорее минимальным временем выполнения. Обратные вызовы, которые вы передаёте этим функциям, не могут выполняться, пока стек в основном потоке не станет пустым.
Как следствие, такой код, как setTimeout (fn, 0), будет выполняться, как только стек будет пуст, а не сразу. Если вы выполните такой код, как setTimeout (fn, 0), но сразу после выполнения цикла, который насчитывает от 1 до 10 миллиардов, ваш колбэк будет выполнен через несколько секунд.
В следующем примере, браузер будет ожидать две секунды перед тем как выполнит анонимную функцию, тогда отобразит сообщение (живой пример, и исходный код):
Указанные вами функции не обязательно должны быть анонимными. Вы можете дать своей функции имя и даже определить её где-нибудь ещё и передать ссылку на функцию в setTimeout (). Следующие две версии фрагмента кода эквивалентны первой:
Это может быть полезно, если у вас есть функция, которую нужно вызывать как по таймауту, так например и в ответ на событие. Но это также может помочь поддерживать ваш код в чистоте, особенно если колбэк тайм-аута занимает больше, чем несколько строк кода.
setTimeout () возвращает значение идентификатора, которое можно использовать для ссылки на тайм-аут позже, например, когда вы хотите его остановить.
Передача параметров в функцию setTimeout ()
Любые параметры, которые вы хотите передать функции, выполняемой внутри setTimeout (), должны быть переданы ей как дополнительные параметры в конце списка.
Например, вы можете реорганизовать предыдущую функцию, чтобы она передавала привет любому имени, переданному ей:
Теперь вы можете передать имя в вызов setTimeout () в качестве третьего параметра:
Очистка таймаутов
Note: См. greeter-app.html для более полной демонстрации, которая позволяет вам указать имя для приветствия и отменить приветствие с помощью отдельной кнопки (см. исходный код).
setInterval()
Очистка интервала
Активное обучение: Создание собственного секундомера!
Вам нужно отображать время, как и раньше, но в этом примере вам нужно:
Несколько подсказок для вас:
Note: Если вы застряли, вы можете увидеть нашу версию (см. также исходный код ).
Что нужно помнить о setTimeout () и setInterval ()
При работе с setTimeout () и setInterval () следует помнить о нескольких вещах. Давайте рассмотрим их.
Рекурсивные таймауты
В приведённом ниже примере используется рекурсивный setTimeout () для запуска переданной функции каждые 100 миллисекунд:
Разница между двумя версиями приведённого выше кода невелика.
Немедленные таймауты
Использование 0 в качестве значения для setTimeout () позволяет планировать выполнение указанной колбэк-функции как можно скорее, но только после того, как будет запущен основной поток кода.
Очистка с помощью clearTimeout() или clearInterval()
clearTimeout () и clearInterval () используют один и тот же список записей для очистки. Интересно, что это означает, что вы можете использовать любой метод для очистки setTimeout () или setInterval ().
requestAnimationFrame()
Note: вы можете найти примеры использования requestAnimationFrame() в этом курсе — например в Рисование графики, and Практика построения объектов.
Метод принимает в качестве аргумента колбэк, который должен быть вызван перед перерисовкой. Это общий шаблон, в котором он используется:
Идея состоит в том, чтобы определить функцию, в которой ваша анимация обновляется (например, ваши спрайты перемещаются, счёт обновляется, данные обновляются или что-то ещё). Затем вы вызываете его, чтобы начать процесс. В конце функционального блока вы вызываете requestAnimationFrame () со ссылкой на функцию, переданной в качестве параметра, и это даёт браузеру указание вызвать функцию снова при следующей перерисовке дисплея. Затем он выполняется непрерывно, поскольку код рекурсивно вызывает requestAnimationFrame ().
Однако, если вы делаете что-то более сложное, включающее объекты, которые не доступны напрямую в the DOM (такие как 2D Canvas API или WebGL ), requestAnimationFrame() предпочтительный вариант в большинстве случаев.
Как быстро работает ваша анимация?
Плавность анимации напрямую зависит от частоты кадров анимации и измеряется в кадрах в секунду (fps). Чем выше это число, тем плавное будет выглядеть ваша анимация до точки.
Поскольку большинство экранов имеют частоту обновления 60 Гц, максимальная частота кадров, к которой вы можете стремиться, составляет 60 кадров в секунду (FPS) при работе с веб-браузерами. Однако большее количество кадров означает больше обработки, которая часто может вызывать заикание и пропуски, также известные как пропадание кадров или заедание.
Если у вас есть монитор с частотой обновления 60 Гц и вы хотите достичь 60 кадров в секунду, у вас есть около 16,7 миллисекунд (1000/60) для выполнения кода анимации для рендеринга каждого кадра. Это напоминание о том, что вам нужно помнить об объёме кода, который вы пытаетесь запустить во время каждого прохождения цикла анимации.
Чем отличается requestAnimationFrame() от setInterval() and setTimeout()?
Давайте поговорим ещё немного о том, чем метод requestAnimationFrame () отличается от других методов, используемых ранее. Глядя на наш код сверху:
Такой же код с использованием setInterval() :
В том числе временная метка
Это полезно, поскольку позволяет запускать вещи в определённое время и в постоянном темпе, независимо от того, насколько быстрым или медленным может быть ваше устройство. Общий шаблон, который вы бы использовали, выглядит примерно так:
Поддержка браузерами
Простой пример
Возьмите базовый HTML шаблон (такой как этот).
Как добавить задержку в цикл JavaScript?
Я хотел бы добавить задержку/сон внутри while петли:
то, что я хотел бы, это после alert(‘hello’) отображается через 3 секунды после alert(‘hi’) тогда ему нужно подождать 3 секунды во второй раз alert(‘hello’) и так далее.
22 ответов
на setTimeout() функция не-преграждает и возвратит немедленно. Поэтому ваш цикл будет перебирать очень быстро и начнет 3-секундный тайм-аут срабатывает один за другим в быстрой последовательности. Вот почему ваши первые оповещения появляются через 3 секунды, а все остальные следуют последовательно без каких-либо задержек.
вы можете использовать что-то вроде этого:
вы также можете привести его в порядок, используя функцию самостоятельного вызова, передача числа итераций в качестве аргумента:
попробуйте что-то вроде этого:
если вы используете ES6, вы можете использовать let достижения:
другой способ-умножить время до таймаута, но обратите внимание, что это не люблю спать. Код после того, как цикл будет выполнен немедленно, только выполнение функции обратного вызова откладывается.
Я думаю, тебе нужно что-то вроде этого:
Примечание: использование предупреждений останавливает выполнение javascript до закрытия предупреждения. Это может быть больше кода, чем вы просили, но это надежное многоразовое решение.
Это будет работать
Так как ES7 есть лучший способ ждут петли:
обратите внимание, что ES7 редко поддерживается сегодня, поэтому вам нужно транспилировать с Babel, чтобы использовать его везде.
в ES6 (ECMAScript 2015) Вы можете выполнять итерацию с задержкой с помощью генератор и интервал.
генераторы, новая функция ECMAScript 6, являются функциями, которые могут быть сделал паузу и продолжил: Вызов genFunc не выполняет его. Вместо этого возвращает так называемый объект generator, который позволяет нам управлять genFunc исполнение. genFunc () первоначально приостанавливается в начале тело. В genObj способ.next () продолжает выполнение genFunc, пока следующий выход. (исследуя ES6)
пример кода:
поэтому, если вы используете ES6, это самый элегантный способ достичь цикла с задержкой (на мой взгляд).
Я делаю это с сине Promise.delay и рекурсии.
просто подумал, что я отправлю свои два цента здесь. Эта функция выполняет итеративный цикл с задержкой. См.этот jsfiddle. Функция выглядит следующим образом:
было бы эквивалентно:
Вы можете использовать RxJS интервал оператор. Интервал испускает целое число через каждые x секунд, и take Is указывает количество раз, когда он должен испускать числа
модифицированная версия ответа Даниэля Вассалло, с переменными, извлеченными в параметры, чтобы сделать функцию более многоразовой:
сначала определим некоторые существенные переменные:
Далее вы должны определить функцию, которую вы хотите запустить. Это будет передано i, текущий индекс цикла и длина цикла, Если вам это нужно:
самоисполнимости версия
функциональная версия
вот как я создал бесконечный цикл с задержкой, которая прерывается при определенном условии:
ключ здесь состоит в том, чтобы создать новое обещание, которое разрешается таймаутом, и ждать его разрешения.
очевидно, вам нужна поддержка async/await для этого. Работает в узле 8.
для общего использования » забудьте обычные циклы «и используйте эту комбинацию» setInterval «включает»setTimeOut»: вот так (из моих реальных задач).
PS: поймите, что реальное поведение (setTimeOut): все они начнут в то же время «три бла бла бла начнут отсчет в тот же момент», поэтому сделайте другой тайм-аут, чтобы организовать выполнение.
Таймеры JavaScript: все что нужно знать
Здравствуйте, коллеги. Давным-давно на Хабре уже переводилась статья под авторством Джона Резига как раз на эту тему. Прошло уж 10 лет, а тема по-прежнему требует разъяснений. Поэтому предлагаем интересующимся почитать статью Самера Буны, в которой дается не только теоретический обзор таймеров в JavaScript (в контексте Node.js), но и задачи на них.
Несколько недель назад я опубликовал в Твиттере следующий вопрос с одного собеседования:
«Где находится исходный код функций setTimeout и setInterval? Где бы вы его искали? Погуглить нельзя :)»
***Ответьте на него для себя, а потом читайте дальше ***
Кому-то может показаться, что это просто плохой вопрос с собеседования – какой вообще прок знать подобное?! Я, как JavaScript-разработчик, думаю так: предполагается, что вы должны это знать, поскольку обратное может свидетельствовать, что вы не вполне понимаете, как V8 (и другие виртуальные машины) взаимодействует с браузерами и Node.
Рассмотрим несколько примеров и решим парочку задач на таймеры, давайте?
Для запуска примеров из этой статьи можно воспользоваться командой node. Большинство рассмотренных здесь примеров фигурируют в моем курсе Getting Started with Node.js на Pluralsight.
Отложенное выполнение функции
Таймеры – это функции высшего порядка, при помощи которых можно откладывать или повторять выполнение других функций (таймер получает такую функцию в качестве первого аргумента).
Вот пример отложенного выполнения:
В этом примере при помощи setTimeout вывод приветственного сообщения откладывается на 4 секунды. Второй аргумент setTimeout — это задержка (в мс). Я умножаю 4 на 1000, чтобы получилось 4 секунды.
Первый аргумент setTimeout – функция, выполнение которой будет откладываться.
Если выполнить файл example1.js командой node, Node приостановится на 4 секунды, а затем выведет приветственное сообщение (после чего последует выход).
При выполнении example2.js командой node фраза “Node.js rocks” будет выведена на экран через 2 секунды.
Задача на таймеры #1
В вашем решении можно определить всего одну функцию, содержащую встроенные функции. Это означает, что множество вызовов setTimeout должны будут использовать одну и ту же функцию.
Вот как я бы решил эту задачу:
У меня theOneFunc получает аргумент delay и использует значение данного аргумента delay в сообщении, выводимом на экран. Таким образом, функция может выводить разные сообщения в зависимости от того, какое значение задержки мы ей сообщим.
Выполнив файл solution1.js командой node, мы выведем на экран требования задачи, причем, первое сообщение появится через 4 секунды, а второе — через 8 секунд.
Повторяем выполнение функции
Вот пример setInterval :
Поскольку при вызове функции таймера назначается действие, это действие также можно отменить, прежде, чем он будет выполнен.
Функция setImmediate поддерживается не во всех браузерах. Не используйте ее в клиентском коде.
Задержка таймера – вещь не гарантированная
Вы заметили, что в предыдущем примере при выполнении операции с setTimeout после 0 мс эта операция происходит не сразу же (после setTimeout ), а только после того, как будет целиком выполнен весь код скрипта (в том числе, вызов clearTimeout )?
Разумеется, на практике так делать очень плохо, но данный пример помогает понять, что задержка setTimeout – это не гарантированное, а, скорее, минимальное значение. Величина 500 мс означает, что задержка продлится минимум 500 мс. На самом деле, скрипту потребуется гораздо больше времени для вывода приветственной строки на экран. Сначала ему придется дождаться, пока завершится блокирующий цикл.
Задача на таймеры #2
Напишите скрипт, который будет выводить сообщение “Hello World” раз в секунду, но всего 5 раз. После 5 итераций скрипт должен вывести сообщение “Done”, после чего процесс Node завершится.
Подсказка: нужен счетчик.
Вот как я бы решил эту задачу:
«Кто» именно вызывает отложенные функции?
При использовании ключевого слова JavaScript this внутри обычной функции, вот так например:
Давайте определим функцию как свойство объекта, чтобы стало немного понятнее:
Теперь, когда при работе с функцией obj.whoCallMe мы будем напрямую использовать ссылку на нее, в качестве вызывающей стороны будет выступать объект obj (идентифицируемый по своему id ):
Кто в данном случае вызывающий?
Ответ будет отличаться в зависимости от того, где выполняется функция таймера. В данном случае просто недопустима зависимость от того, кто — вызывающая сторона. Вы утратите контроль над вызывающей стороной, поскольку именно от реализации таймера будет зависеть, кто в данном случае вызывает вашу функцию. Если протестировать этот код в Node REPL, то вызывающей стороной окажется объект Timeout :
Обратите внимание: это важно лишь в случае, когда ключевое слово JavaScript this используется внутри обычных функций. При использовании стрелочных функций вызывающая сторона вас вообще не должна беспокоить.
Задача на таймеры #3
Напишите скрипт, который будет непрерывно выводить сообщение “Hello World” с варьирующимися задержками. Начните с односекундной задержки, после чего на каждой итерации увеличивайте ее на секунду. На второй итерации задержка будет 2 секунды. На третьей — три, и так далее.
Включите задержку в выводимое сообщение. У вас должен получиться примерно такой вывод:
Hello World. 1
Hello World. 2
Hello World. 3
.
Ограничения: переменные можно определять только при помощи const. При помощи let или var — нельзя.
Поскольку длительность задержки в данной задаче – величина переменная, использовать setInterval здесь нельзя, но можно вручную настроить интервальное выполнение при помощи setTimeout внутри рекурсивного вызова. Первая выполненная функция с setTimeout будет создавать следующий таймер, и так далее.
Вот как можно было бы решить эту задачу:
Задача на таймеры #4
Напишите скрипт, который будет выводить сообщение “Hello World” с такой же структурой задержек, как и в задаче #3, но на этот раз группами по 5 сообщений, а в группах будет основной интервал задержки. Для первой группы из 5 сообщений выбираем исходную задержку в 100 мс, для следующей – 200 мс, для третьей – 300 мс и так далее.
Вот как должен работать этот скрипт:
Включите задержку в выводимое сообщение. У вас должен получиться примерно такой вывод (без комментариев):
Hello World. 100 // При 100 мс
Hello World. 100 // При 200 мс
Hello World. 100 // При 300 мс
Hello World. 100 // При 400 мс
Hello World. 100 // При 500 мс
Hello World. 200 // При 700 мс
Hello World. 200 // При 900 мс
Hello World. 200 // При 1100 мс
.
Функция js setTimout и sleep в JavaScript
setTimout и sleep в JavaScript
Для того, чтобы лучше понять проблему, посмотрите следующий код. Он делает запрос к API GitHub для получения пользовательских данных, затем выводит количество публичных репозиториев и, наконец, пошлет в консоль «Привет!»
Но если запустить такой код, то сначала он выведет на экран «Привет!», а уже потом количество публичных репозиториев.
Так происходит, потому что получение данных от API — это асинхронная операция в JavaScript. Интерпритатор JavaScript встретит команду fetch и отправит запрос. Он не будет ждать завершения запроса и выведет «Привет!» в консоль, и потом через пару сотен миллисекунд выдаст количество рекозиториев.
Создаем простую задержку с помощью js setTimeout
Этот код запишет в консоль «Привет», а затем через 3 секунды «мир» ‐ во многих случаях такой задержки достаточно. Но имейте ввиду, что в js setTimeout ‐ это асинхронный метод. Попробуйте изменить предыдущий код следующим образом:
Этот код выведет: Привет Пока мир.
Вы также можете использовать setTimeout (или его брата setInterval ), чтобы заставить JavaScript подождать пока не будет выполнено какое-либо условие. К примеру, вам нужно дождаться появления определенного элемента на странице:
Управление потоком в современном JavaScript
В JavaScript часто бывают ситуации, когда вам нужно подождать, пока то-то произойдет, а затем сделать что-то в ответ. В примере выше мы использовали анонимную callback функцию. Но что если вам нужно подождать несколько событий? В этом случае синтаксис быстро становится довольно грубым.
Теперь код выполняется сверху вниз.
Sleep в JavaScript
Мы уже готовы остановить поток выполнения программы и заставить JavaScript спать. Вот как мы можем это сделать:
Улучшаем функцию js sleep
Функция делает то, что нам нужно, но у нее есть недостаток: если вам нужна большая задержка, то она может привести к поломке программы. Объединим методы изученные ранее в статье, чтобы сделать менее навязчивую функцию сна.
Этот код выведет в консоль «Привет», подождет 2 секунды, затем выведет «мир». Внутри мы используем Promise.
Планирование: 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 мс в зависимости от браузера и настроек производительности ОС.
Задачи
Вывод каждую секунду
Сделайте два варианта решения.