Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Данные с сервера на странице

Этот урок соединяет две темы: асинхронные запросы и работу со страницей. Типичная задача — загрузить с сервера список товаров или сообщений и показать его. Пока данные идут, пользователь должен видеть, что идёт загрузка, а при сбое — понятное сообщение.

Песочница сайта не выходит в сеть, поэтому в примерах сервер заменён функцией, которая возвращает промис с данными через полсекунды. В конце урока показано, как тот же код выглядит с настоящим fetch().

Состояние загрузки

Пока ответа нет, на странице показывают надпись или индикатор, а когда данные пришли — заменяют его результатом:

<p id="status">Загрузка…</p>
 
<script>
  const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
 
  async function show() {
    await wait(500);
    document.querySelector('#status').textContent = 'Данные загружены';
  }
 
  show();
</script>

Вывод списка

Данные с сервера обычно приходят массивом объектов. По нему строят элементы, как в уроке о создании элементов:

<ul id="users"></ul>
 
<script>
  function loadUsers() {
    const users = [{ name: 'Анна' }, { name: 'Олег' }, { name: 'Мария' }];
    return new Promise((resolve) => setTimeout(() => resolve(users), 500));
  }
 
  async function showUsers() {
    const list = document.querySelector('#users');
    const users = await loadUsers();
 
    for (const user of users) {
      const item = document.createElement('li');
      item.textContent = user.name;
      list.append(item);
    }
  }
 
  showUsers();
</script>

Ошибки

Запрос может не удаться, и пользователь не должен остаться перед вечной надписью «Загрузка…». Ошибку ловят через try...catch и показывают сообщение:

<p id="status">Загрузка…</p>
 
<script>
  function loadUsers() {
    return new Promise((resolve, reject) => {
      setTimeout(() => reject(new Error('Сервер недоступен')), 500);
    });
  }
 
  async function showUsers() {
    const status = document.querySelector('#status');
 
    try {
      const users = await loadUsers();
      status.textContent = `Пользователей: ${users.length}`;
    } catch (error) {
      status.textContent = `Ошибка: ${error.message}`;
    }
  }
 
  showUsers();
</script>

Загрузка по кнопке

Часто данные загружают по действию пользователя. На время запроса кнопку отключают свойством disabled, чтобы её не нажали дважды, а включают обратно в finally:

<button id="load">Показать курс</button>
<p id="rate"></p>
 
<script>
  const button = document.querySelector('#load');
  const rate = document.querySelector('#rate');
 
  function loadRate() {
    return new Promise((resolve) => setTimeout(() => resolve(0.92), 500));
  }
 
  button.addEventListener('click', async () => {
    button.disabled = true;
    rate.textContent = 'Загрузка…';
 
    try {
      const value = await loadRate();
      rate.textContent = `1 $ = ${value} €`;
    } catch (error) {
      rate.textContent = 'Не удалось загрузить курс';
    } finally {
      button.disabled = false;
    }
  });
</script>

С настоящим fetch

В реальном проекте вместо функции-заглушки стоит запрос к серверу. Остальной код не меняется:

async function loadUsers() {
  const response = await fetch('/api/users');
 
  if (!response.ok) {
    throw new Error(`Ошибка ${response.status}`);
  }
  return response.json();
}

Поэтому загрузку данных удобно выносить в отдельную функцию: её можно заменить, не трогая код, который рисует страницу.

Частая ошибка

Добавлять элементы при каждой загрузке, не очистив старые. После второго нажатия список удвоится. Перед выводом новых данных очищайте контейнер методом replaceChildren():

<button>Обновить</button>
<ul></ul>
 
<script>
  const list = document.querySelector('ul');
 
  document.querySelector('button').addEventListener('click', () => {
    list.replaceChildren();
 
    for (const name of ['Чай', 'Кофе']) {
      const item = document.createElement('li');
      item.textContent = name;
      list.append(item);
    }
  });
</script>

Уберите строку с replaceChildren() и понажимайте кнопку — список начнёт расти.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.