Многие языки программирования имеют функцию ожидания, которая задержит выполнение программы на определенное количество секунд. Однако эта функциональность отсутствует в JavaScript из-за ее асинхронного характера. В этой статье мы кратко рассмотрим, почему это может быть, а затем как мы можем реализовать функцию sleep
Мы переписали это популярное руководство с нуля, чтобы предоставить лучшие и самые современные советы. Эта статья была обновлена в ноябре 2019 года.
Понимание модели исполнения JavaScript
Прежде чем мы начнем, важно убедиться, что мы правильно понимаем модель выполнения JavaScript.
Рассмотрим следующий код Ruby:
sleep
Как и следовало ожидать, этот код делает запрос к API GitHub для получения моих пользовательских данных. Затем он анализирует ответ, выводит количество открытых репозиториев, связанных с моей учетной записью GitHub, и, наконец, выводит на экран «Hello!». Исполнение идет сверху вниз.
Сравните это с эквивалентной версией JavaScript:
require 'net/http'
require 'json'
url = 'https://api.github.com/users/jameshibbard'
uri = URI(url)
response = JSON.parse(Net::HTTP.get(uri))
puts response['public_repos']
puts "Hello!"
Если вы запустите этот код, он выведет «Hello!» На экран, а затем количество публичных репо, связанных с моей учетной записью GitHub.
Это связано с тем, что выборка данных из API является асинхронной операцией в JavaScript. Интерпретатор JavaScript встретит команду fetch('https://api.github.com/users/jameshibbard')
Однако он не будет ждать завершения запроса. Скорее, он продолжит свой путь, выведет «Hello!» На консоль, а затем, когда запрос вернется через пару сотен миллисекунд, выдаст количество репо.
.then(res => res.json())
.then(json => console.log(json.public_repos));
console.log("Hello!");
Если что-то из этого является новостью для вас, вы должны посмотреть этот замечательный доклад на конференции: « Какого черта цикл событий в любом случае? ,
Возможно, вам не нужна функция сна
Теперь, когда мы лучше понимаем модель выполнения JavaScript, давайте посмотрим, как JavaScript обрабатывает задержки и асинхронные операции.
Создайте простую задержку, используя fetch
Стандартный способ создания задержки в JavaScript — использовать метод setTimeout
Например:
setTimeout
Это будет записывать «Hello» на консоль, затем через две секунды «World!». И во многих случаях этого достаточно: сделать что-то, подождать, затем сделать что-то еще. Сортировка!
Однако имейте в console.log("Hello");
setTimeout(() => { console.log("World!"); }, 2000);
setTimeout
Попробуйте изменить предыдущий код следующим образом:
console.log("Hello");
setTimeout(() => { console.log("World!"); }, 2000);
console.log("Goodbye!");
Это войдет:
Hello
Goodbye!
World!
В ожидании вещей с setTimeout
Также возможно использовать setTimeout
setInterval
Например, вот как вы можете использовать setTimeout
function pollDOM () {
const el = document.querySelector('my-element');
if (el.length) {
// Do something with el
} else {
setTimeout(pollDOM, 300); // try again in 300 milliseconds
}
}
pollDOM();
Это предполагает, что элемент появится в какой-то момент. Если вы не уверены, что это так, вам нужно посмотреть на отмену таймера (используя clearTimeout
clearInterval
Если вы хотите узнать больше о методе JavaScript setTimeout
нашему руководству, в котором есть множество примеров, которые помогут вам в этом.
Управление потоком в современном JavaScript
При написании JavaScript часто бывает необходимо подождать, пока что-то произойдет (например, данные должны быть получены из API), а затем сделать что-то в ответ (например, обновить пользовательский интерфейс для отображения данных).
В приведенном выше примере для этой цели используется функция анонимного обратного вызова, но если вам нужно подождать, пока произойдет несколько событий, синтаксис быстро становится довольно грубым, и вы попадаете в ад обратного вызова .
К счастью, язык значительно изменился за последние несколько лет и теперь предлагает нам новые конструкции, чтобы избежать этого.
Например, используя async await, мы можем переписать исходный код для получения информации из API GitHub:
(async () => {
const res = await fetch(`https://api.github.com/users/jameshibbard`);
const json = await res.json();
console.log(json.public_repos);
console.log("Hello!");
})();
Теперь код выполняется сверху вниз. Интерпретатор JavaScript ожидает завершения сетевого запроса, и сначала регистрируется количество открытых репозиториев, а затем сообщение «Hello!».
Если это нечто большее, чем вы пытаетесь выполнить, я рекомендую вам прочитать нашу статью « Управление потоком данных в современном JS: обратные вызовы для обещаний в асинхронное / ожидание» .
Сон в родном JavaScript
Если вы все еще со мной, то, я думаю, вы вполне готовы заблокировать поток выполнения и заставить JavaScript ждать его.
Вот как вы можете это сделать:
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
console.log("Hello");
sleep(2000);
console.log("World!");
Как и ожидалось, будет отображаться «Hello», пауза на две секунды, затем «World!»
Он работает с помощью метода Date.now, чтобы получить количество миллисекунд, прошедших с 1 января 1970 года, и присвоить это значение переменной date
Затем он создает пустую переменную currentDate
do ... while
В цикле он многократно получает количество миллисекунд, прошедших с 1 января 1970 года, и присваивает значение ранее объявленной переменной currentDate
Цикл будет продолжаться, пока разница между date
currentDate
Работа сделана, верно? Ну, не совсем …
Лучшая функция сна
Возможно, этот код делает именно то, на что вы надеетесь, но имейте в виду, у него есть большой недостаток: цикл будет блокировать поток выполнения JavaScript и гарантировать, что никто не сможет взаимодействовать с вашей программой до ее завершения. Если вам нужна большая задержка, есть вероятность, что это может даже привести к поломке.
Так что делать?
Ну, также возможно объединить методы, изученные ранее в статье, чтобы сделать менее навязчивую функцию сна:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
console.log("Hello");
sleep(2000).then(() => { console.log("World!"); });
Этот код будет записывать «Hello», подождать две секунды, затем записать «World!». Под капотом мы используем метод setTimeout
Promise по истечении заданного количества миллисекунд.
Обратите внимание, что нам нужно использовать обратный вызов then
Мы также можем связать больше обратных вызовов на первый:
console.log("Hello");
sleep(2000)
.then(() => { console.log("World!"); })
.then(() => {
sleep(2000)
.then(() => { console.log("Goodbye!"); })
});
Это работает, но выглядит некрасиво. Мы можем сделать это с помощью async ... await
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function delayedGreeting() {
console.log("Hello");
await sleep(2000);
console.log("World!");
await sleep(2000);
console.log("Goodbye!");
}
delayedGreeting();
Это выглядит лучше, но означает, что любой код, использующий функцию sleep
async
Конечно, оба этих метода по-прежнему имеют недостаток (или особенность) в том, что они не приостанавливают выполнение программы в целом. Спит только ваша функция:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function delayedGreeting() {
console.log("Hello");
await sleep(2000);
console.log("World!");
}
delayedGreeting();
console.log("Goodbye!");
Код выше регистрирует следующее:
Hello
Goodbye!
World!
Вывод
Проблемы синхронизации в JavaScript являются причиной многих головных болей разработчиков, и то, как вы справляетесь с ними, зависит от того, чего вы пытаетесь достичь.
Хотя функция сна присутствует во многих других языках, я рекомендую вам принять асинхронную природу JavaScript и стараться не бороться с языком. Это на самом деле довольно приятно, когда вы к этому привыкнете
Также, для дальнейшего чтения, посмотрите эти ссылки, которые вдохновили часть кода в этой статье:
Если у вас есть какие-либо вопросы, перейдите на форумы SitePoint и начните обсуждение.