Сон, паузы и ожидание в JavaScript

Затягивание, сон, паузы и ожидание в JavaScript

Многие языки программирования имеют функцию сна, которая будет задерживать выполнение программы на несколько секунд. Однако эта функциональность отсутствует в JavaScript из-за ее асинхронного характера.

В этой статье, мы кратко рассмотрим почему так может быть, а затем как мы можем реализовать функцию сна самостоятельно.

Понимания модели исполнения JavaScript

Перед тем, как мы приступим, важно убедится, что мы правильно понимаем модель выполнения JavaScript.

Рассмотрим следующий код Ruby:

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!"

Как и следовало ожидать, этот код отправляет запрос к GitHub API для получения моих пользовательских данных. Затем он анализирует ответ, выводит количество открытых репозиториев, относящихся к моей учетной записи GitHub, и, наконец, выводит на экран «Hello!». Исполнение идет сверху вниз.

Сравните его с эквивалентной версией в JavaScript:

fetch('https://api.github.com/users/jameshibbard')           .then(res => res.json())         
   .then(json => console.log(json.public_repos)); 
console.log("Hello!");

Если вы запустите этот код, он выведет «Hello!» На экран, а затем количество публичных репозитеров, связанных с моей учетной записью GitHub.

Это связано с тем, что выборка данных из API является асинхронной операцией в JavaScript. Интерпретатор JavaScript встретит команду fetch и отправит запрос. Однако, он не будет ждать завершения запроса. Скорее всего, он продолжит свой путь, выведет «Hello!» на консоль, а затем, когда запрос вернется через пару сотен миллисекунд, выдаст количество репозитеров.

Возможно, Вам не нужна функция сна

Теперь, когда мы лучше понимаем модель выполнения JavaScript, давайте посмотрим, как JavaScript обрабатывает задержки и асинхронные операции.

Создание простой задержки, используя setTimeout

Стандартный способ создания задержки в JavaScript это использовать метод setTimeout. Например:

console.log("Hello"); 
setTimeout(() => {  console.log("World!"); }, 2000);

Он будет показывать «Hello!» на консоли, а затем через две секунд «World!». И во многих случаях этого достаточно: сделать что-то, подождать, затем сделать что-то еще. Выполнено!

Однако, имейте ввиду, что метод setTimeout асинхронный. Попробуйте изменить предыдущий код следующим образом:

console.log("Hello"); 
setTimeout(() => { console.log("World!"); }, 2000); 
console.log("Goodbye!");

Оно покажет это:

Hello 
Goodbye! 
World!

В ожидании вещей с setTimeout

Также возможно использовать setTimeout (или его двоюродного брата setInterval), чтобы заставить JavaScript ждать пока не будет выполнено условие. Например, вот как вы можете использовать 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

При написании JavaScript часто бывает необходимо подождать, пока что-то произойдет (например, данные должны быть получены из API), а затем сделать что-то в ответ (например, обновить пользовательский интерфейс для отображения данных).

В приведенном выше примере используется функция анонимного обратного вызова, но если Вам нужно подождать пока произойдет несколько событий, синтаксис быстро становится довольно грубым, и вы попадаете в ад обратного вызова.

К счастью, язык значительно изменился за последние несколько лет и предлагает нам новые конструкции, чтобы избежать этого.

Например, используя асинхронное ожидание, мы можем переписать исходный код, чтобы получить информацию из GitHub API:

(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!».

Сон в изначальном 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 года, и присвоить это значение переменной дате. После он создает пустую переменную 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();

Это выглядит хорошо, но означает, что любой код, использующий функцию сна, должен быть помечен как асинхронный.

Конечно, оба этих метода по-прежнему имеют недостаток (или особенность) в том, что они не приостанавливают выполнение всей программы. Спит только Ваша функция:

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 и стараться не бороться с языком. Это на самом деле, довольно приятно, когда Вы привыкните к этому.


.

  • November 30, 2019