Отдельное место в асинхронном мире занимают таймеры. Они позволяют отложить выполнение какой-либо функции "на потом". Наиболее важная функция для работы с таймерами — setTimeout(f, delay)
const f = () => console.log('hey!');
setTimeout(f, 1000);
В коде выше функция f
выполнится не раньше, чем через секунду. Об этом нам говорит второй параметр, в который передаётся время, указанное в миллисекундах, после которого запустится функция, указанная первым параметром. По историческим причинам у таймеров есть минимальная задержка, которую они соблюдают всегда, и она равна четырём миллисекундам. Другими словами, нет разницы между вызовами setTimeout(f, 1)
, setTimeout(f, 3)
и setTimeout(f, 4)
— во всех этих случаях минимальная задержка равна 4
.
Для чего нужны таймеры? У них много разных применений. Если говорить про браузер, то это могут быть автоматически скрываемые элементы: например, нотификации. Другой пример — это регулярный (например, раз в 5 секунд) Ajax-запрос для получения новых данных. На сервере таймеры используются реже, но тоже встречаются: с помощью них можно разбить объёмную синхронную операцию на несколько кусков, давая возможность выполниться другому коду.
В операционных системах такое поведение называется кооперативной многозадачностью. Она позволяет создавать ощущение параллельного выполнения кода, даже если этого не происходит.
Функция, переданная в таймер, выполняется не в текущем стеке вызовов, а значит к таймерам применимы все те особенности и подходы, о которых мы говорили ранее. Ошибки, возникающие в таймерах, невозможно отследить с помощью try/catch, для этого нужно использовать колбеки.
const f = () => console.log('hey!');
console.log('before timeout');
setTimeout(f, 1000);
console.log('after timeout');
// скрипт не заканчивается, а дожидается выполнения таймеров
Запуск:
node index.js
before timeout
after timeout
hey!
Попробуйте ответить на такой вопрос. Могут ли таймеры гарантировать точный запуск через указанный промежуток времени? На самом деле не могут. Все зависит от того, что выполняется прямо сейчас. Проверкой таймеров занимается рантайм в тот момент, когда в текущем стеке вызовов не осталось кода. Если запустить тяжелое вычисление, которое не прекращается долго, то все колбеки, все таймеры, будут ждать пока вычисление закончится. Фактически это означает, что в таймерах задается минимальное время, после которого их можно запускать.
Эта особенность имеет два важных следствия:
Таймеры можно не только создавать, но и отменять. Вызов setTimeout
возвращает специальное значение — идентификатор таймера. Если передать его в функцию clearTimeout
, то таймер отменится:
const f = () => console.log('hey!');
console.log('before timeout');
// В браузере идентификатор таймера это числовое значение
// В node.js это объект
const timerId = setTimeout(f, 1000);
console.log('after timeout');
clearTimeout(timerId);
Запуск:
node index.js
before timeout
after timeout
То, что асинхронная функция вернула идентификатор таймера, не противоречит тому, что мы ранее говорили про возврат в асинхронных функциях. Асинхронная функция не может вернуть результат выполнения асинхронной операции, но может вернуть что-нибудь другое, что делалось синхронно внутри нее. В случае таймеров, идентификатор таймера возвращается синхронно, а вот сам таймер выполняется асинхронно.
Частая ошибка новичков в том, что они передают в таймер не саму функцию, а делают её вызов. Обычно она встречается тогда, когда в функцию нужно передать некоторые заранее определённые аргументы:
const f = (message) => console.log(message);
console.log('before timeout');
setTimeout(f('hey!'), 1000);
console.log('after timeout');
Запуск:
node index.js
before timeout
hey!
timers.js:390
throw new ERR_INVALID_CALLBACK();
^
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
До последнего лога дело не дошло, потому что скрипт упал на вызове setTimeout
, так как он ожидал на вход функцию, а пришла не функция (вызов в примере вернул значение undefined
).
Передать данные внутрь функции можно тремя способами:
Дополнительные параметры в setTimeout
Все аргументы, переданные в setTimeout
после второго аргумента (времени), автоматически становятся аргументами функции, которую вызовет таймер.
const f = (a, b) => console.log(a + b);
setTimeout(f, 1000, 5, 8);
// => 13
Функция-обёртка
Наиболее распространённый способ — создание функции-обёртки. Такой способ лучше предыдущего из-за его прозрачности: сразу видно, что происходит.
const f = (a, b) => console.log(a + b);
setTimeout(() => f(5, 8), 1000);
// => 13
bind
Последний способ — использовать функцию bind
. Основное предназначение этой функции — смена контекста функции. Но она может использоваться и для частичного применения функции:
const f = (a, b) => console.log(a + b);
// Первый параметр null потому что контекст не меняется
setTimeout(f.bind(null, 5, 8), 1000);
// => 13
Вызов этой функции возвращает новую функцию с применёнными аргументами.
Важно понимать, что таймер не делает операцию (ту, что выполняется при вызове функции, переданной в setTimeout) асинхронной — таймер лишь откладывает время её выполнения. Если сама операция синхронная, то после запуска она заблокирует основной поток выполнения программы, и все остальные будут ждать её завершения.
Функция setInterval
имеет точно такую же сигнатуру, как и setTimeout
. Смысл аргументов — тот же самый. Разница в том, что setInterval
автоматически запускает функцию не один раз, а до тех пор, пока её явно не остановят через clearInterval
. Время между запусками равно значению второго параметра.
const id = setInterval(() => console.log(new Date()), 5000);
setTimeout(() => clearInterval(id), 16000);
// node index.js
// 2019-06-05T19:05:28.149Z
// 2019-06-05T19:05:33.172Z
// 2019-06-05T19:05:38.177Z
Таймер можно остановить изнутри, использовав в колбеке его id.
let counter = 0;
const id = setInterval(() => {
counter += 1;
if (counter === 4) {
clearInterval(id);
return;
}
console.log(new Date());
}, 5000);
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт