Статьи

Задержка, сон, пауза и ожидание в JavaScript

Многие языки программирования имеют функцию ожидания, которая задержит выполнение программы на определенное количество секунд. Однако эта функциональность отсутствует в 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')
.then(res => res.json())
.then(json => console.log(json.public_repos));
console.log("Hello!");
Однако он не будет ждать завершения запроса. Скорее, он продолжит свой путь, выведет «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

Также возможно использовать setTimeoutsetInterval Например, вот как вы можете использовать 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();

Это предполагает, что элемент появится в какой-то момент. Если вы не уверены, что это так, вам нужно посмотреть на отмену таймера (используя clearTimeoutclearInterval

Если вы хотите узнать больше о методе 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 Затем он создает пустую переменную currentDatedo ... while В цикле он многократно получает количество миллисекунд, прошедших с 1 января 1970 года, и присваивает значение ранее объявленной переменной currentDate Цикл будет продолжаться, пока разница между datecurrentDate

Работа сделана, верно? Ну, не совсем …

Лучшая функция сна

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

Так что делать?

Ну, также возможно объединить методы, изученные ранее в статье, чтобы сделать менее навязчивую функцию сна:

 function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

console.log("Hello");
sleep(2000).then(() => { console.log("World!"); });

Этот код будет записывать «Hello», подождать две секунды, затем записать «World!». Под капотом мы используем метод setTimeoutPromise по истечении заданного количества миллисекунд.

Обратите внимание, что нам нужно использовать обратный вызов 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();

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

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

 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 и начните обсуждение.