Асинхронный JavaScript
Last updated
Last updated
При разработке часто приходится иметь дело с асинхронным поведением, которое может смущать новичков, у которых есть опыт работы с синхронным кодом.
Синхронный код выполняется последовательно, каждая инструкция ожидает пока выполнится предыдущая.
Асинхронный код выполняется не дожидаясь выполнения предыдущих инструкций.
В синхронном коде, если есть две инструкции: L1
, за которой следует L2
, L2
не может начать выполнение до тех пор, пока L1
не завершит выполнение.
Представьте очередь покупки билетов на поезд. Вы не можете начать покупать билет на поезд до тех пор, пока не наступит ваша очередь. Точно так же люди, стоящие за вами, не могут начать покупать билеты до тех пор, пока не купите вы.
Этот код — синхронный, инструкции обрабатываются последовательно.
В асинхронном коде, если есть две инструкции: L1
, за которой следует L2
, где L1
планирует выполнение какой-либо задачи в будущем, то L2
исполнится раньше чем L1
.
Представьте обед в ресторане. Вы и другие посетители, заказываете еду. Вам не нужно ждать, пока им принесут еду, прежде чем заказывать. Точно так же другие посетители не должны ждать, пока вы получите свое блюдо и поедите, прежде чем они смогут заказать. Каждый получит свое блюдо, как только его закончат готовить.
Следующий код — асинхронный. С функцией setTimeout
мы познакомимся дальше. Сейчас о ней нам нужно знать только то, что она принимает 2 параметра, callback-функцию, которая будет вызвана по истечении времени, которое мы передаем вторым аргументом.
Не путайте асинхронность и многопоточность. Многие думают, что многопоточность и асинхронность - это одно и то же, но это не так.
Приведем простую аналогию, которая все расставит по своим местам. Представьте, что вы шеф в ресторане и приходит заказ на яйца и тосты.
Синхронный подход: сначала вы готовите яйца, затем тосты.
Асинхронный подход (однопоточный): вы начинаете готовить яйца и устанавливаете таймер, затем вы начинаете готовить тосты и так же устанавливаете таймер. Пока яйца и тосты готовятся, вы убираете на кухне. Когда таймеры срабатывают, вы снимаете с огня яйца и достаете тосты из тостера и подаете их.
Асинхронный подход (многопоточный): вы нанимаете еще двух поваров, одного для приготовления яиц и одного для приготовления тостов. Теперь у вас есть проблема управления поварами (потоками), чтобы они не конфликтовали друг с другом на кухне при совместном использовании ресурсов.
В асинхронных многопоточных процессах вы назначаете задачи работникам. В асинхронных однопоточных процессах у вас есть график задач, где некоторые задачи зависят от результатов других. По мере выполнения каждой задачи вызывается код, который планирует следующую задачу, которая может быть запущена с учетом результатов только что выполненной задачи. Но вам нужен только один работник для выполнения всех задач, а не один работник на задачу.
Внутренний таймер-планировщик позволяет откладывать вызов функции на определенный период времени. Для этого есть таймауты и интервалы, которые позволяют контролировать когда и как часто вызывается функция.
Таймаут позволяет запускать функцию по истечении определенного времени.
Принимает как минимум два аргумента: callback-функцию callback
и время задержки delay
в миллисекундах, по завершению которого один раз будет вызвана callback-функция.
Дополнительные аргументы будут переданы callback-функции.
Возвращает цифровой идентификатор созданного таймера, который используется для его удаления.
Если нам, по какой-то причине, нужно отменить вызов функции внутри таймаута, используется функция clearTimeout(id)
, которая получает идентификатор таймера и очищает (удаляет) его.
Поскольку мы использовали clearTimeout
, а он исполнится раньше, таймер с timerId
будет очищен и станет не активен. Он будет действовать так, как будто он никогда не существовал и поэтому в консоль ничего не выведется.
Интервалы — это более простой способ повторения кода снова и снова с установленным промежутком времени повторений.
Функция setInterval
имеет синтаксис, аналогичный setTimeout
.
Смысл аргументов тот же самый. Но, в отличие от setTimeout
, она запускает выполнение функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом clearInterval(id)
.
setTimeout
и setInterval
- это методы глобального объекта window
, именно поэтому мы не вызываем их как window.setTimeout()
.
Необходимо быть внимательными с this
внутри функций-интервалов. При классической callback-функции, this
получит ссылку на window
, а при стрелочной функции this
будет из внешней области видимости.
У браузерного таймера есть минимальная возможная задержка. В современных браузерах она меняется от примерно 0мс
до 4мс
. В более старых она может быть больше и достигать 15мс
. По стандарту, минимальная задержка составляет 4мс
. Так что разницы между setTimeout(..., 1)
и setTimeout(..., 4)
нет.
В ряде ситуаций таймер будет срабатывать реже, чем обычно. Задержка между вызовами setInterval(..., 4)
может быть не 4мс
, а 30мс
или даже 1000мс
. При слишком большой загрузке процессора некоторые запуски функций-интервалов будут пропущены.
Большинство браузеров, в первую очередь десктопных, продолжают выполнять setTimeout
и setInterval
, даже если вкладка неактивна. При этом ряд из них снижают минимальную частоту таймера до 1 раза в секунду. Получается, что в фоновой вкладке будет срабатывать таймер, но редко.