Что такое async/await?

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Средний
#JavaScript #Асинхронность #База JS

Краткий ответ

Async/await — это синтаксический сахар над промисами в JavaScript, который позволяет писать асинхронный код так, как будто он синхронный. Ключевое слово async перед функцией делает её асинхронной и гарантирует возврат промиса, а await приостанавливает выполнение функции до разрешения промиса. Это упрощает чтение и написание асинхронного кода, избегая цепочек .then().

Основные преимущества:

  • Читаемость — линейная структура кода вместо цепочек .then()
  • Упрощенная обработка ошибок — использование try/catch вместо .catch()
  • Удобство отладки — сохранение стека вызовов

Полный ответ

Async/await — это современный способ работы с асинхронным кодом в JavaScript, представленный в ES2017. Он позволяет писать асинхронный код, который выглядит и ведет себя как синхронный, делая его более читаемым и поддерживаемым.

Как работает async/await

Ключевые слова async и await всегда используются вместе:

// Объявление асинхронной функции
async function fetchData() {
  // await приостанавливает выполнение до разрешения промиса
  const response = await fetch('/api/data');
  const data = await response.json();
  return data;
}
 
// Использование
fetchData()
  .then(result => console.log('Данные:', result))
  .catch(error => console.error('Ошибка:', error));

Основные сценарии использования

1. Работа с HTTP-запросами

Async/await особенно удобен при работе с API:

// С промисами
function getUserDataWithPromises(userId) {
  return fetch(`/api/users/${userId}`)
    .then(response => response.json())
    .then(user => {
      return fetch(`/api/users/${userId}/posts`)
        .then(response => response.json())
        .then(posts => ({ user, posts }));
    })
    .catch(error => console.error('Ошибка:', error));
}
 
// С async/await
async function getUserDataWithAsync(userId) {
  try {
    const userResponse = await fetch(`/api/users/${userId}`);
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/users/${userId}/posts`);
    const posts = await postsResponse.json();
    
    return { user, posts };
  } catch (error) {
    console.error('Ошибка:', error);
    throw error;
  }
}

2. Последовательное выполнение операций

Когда операции должны выполняться одна за другой:

async function processUserData() {
  try {
    // Шаг 1: Получаем данные пользователя
    const user = await fetchUser();
    console.log('Пользователь получен:', user.name);
    
    // Шаг 2: Получаем профиль пользователя
    const profile = await fetchProfile(user.id);
    console.log('Профиль получен:', profile.email);
    
    // Шаг 3: Получаем настройки пользователя
    const preferences = await fetchPreferences(profile.id);
    console.log('Настройки получены:', preferences.theme);
    
    return { user, profile, preferences };
  } catch (error) {
    console.error('Ошибка при обработке данных пользователя:', error);
    throw error;
  }
}

3. Параллельное выполнение операций

Иногда нужно выполнить несколько независимых операций параллельно:

// Последовательное выполнение (медленнее)
async function sequentialExecution() {
  const users = await fetchUsers();        // 1 секунда
  const posts = await fetchPosts();        // 1 секунда
  const comments = await fetchComments();  // 1 секунда
  // Общее время: ~3 секунды
  return { users, posts, comments };
}
 
// Параллельное выполнение (быстрее)
async function parallelExecution() {
  const [users, posts, comments] = await Promise.all([
    fetchUsers(),      // 1 секунда
    fetchPosts(),      // 1 секунда
    fetchComments()    // 1 секунда
  ]);
  // Общее время: ~1 секунда
  return { users, posts, comments };
}

Практические примеры

Обработка форм с валидацией

async function handleSubmit(formData) {
  try {
    // Валидация данных
    const validation = await validateForm(formData);
    if (!validation.isValid) {
      throw new Error('Форма содержит ошибки');
    }
    
    // Отправка данных
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify(formData),
      headers: { 'Content-Type': 'application/json' }
    });
    
    if (!response.ok) {
      throw new Error('Ошибка отправки данных');
    }
    
    const result = await response.json();
    console.log('Форма успешно отправлена:', result);
    return result;
  } catch (error) {
    console.error('Ошибка при отправке формы:', error);
    showErrorToUser(error.message);
  }
}

Работа с localStorage и API

async function loadUserData(userId) {
  try {
    // Сначала проверяем кэш
    const cachedData = localStorage.getItem(`user_${userId}`);
    if (cachedData) {
      console.log('Данные загружены из кэша');
      return JSON.parse(cachedData);
    }
    
    // Если нет в кэше, загружаем из API
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error('Ошибка загрузки данных пользователя');
    }
    
    const userData = await response.json();
    
    // Сохраняем в кэш
    localStorage.setItem(`user_${userId}`, JSON.stringify(userData));
    
    return userData;
  } catch (error) {
    console.error('Ошибка загрузки данных пользователя:', error);
    throw error;
  }
}

Обработка нескольких источников данных

async function fetchUserDataFromMultipleSources(userId) {
  try {
    // Загружаем данные из основного API
    const mainDataPromise = fetch(`/api/users/${userId}`).then(res => res.json());
    
    // Загружаем данные из резервного API
    const backupDataPromise = fetch(`/backup-api/users/${userId}`).then(res => res.json());
    
    // Ждем первый успешный результат
    const userData = await Promise.any([mainDataPromise, backupDataPromise]);
    
    return userData;
  } catch (error) {
    if (error instanceof AggregateError) {
      console.error('Все источники данных недоступны:', error.errors);
    } else {
      console.error('Ошибка загрузки данных:', error);
    }
    throw error;
  }
}

Распространенные ошибки и решения

1. Забытый await

// ❌ Забытый await
async function badExample() {
  const data = fetch('/api/data'); // Это промис, а не данные!
  console.log(data.name); // undefined
}
 
// ✅ Правильное использование await
async function goodExample() {
  const response = await fetch('/api/data');
  const data = await response.json();
  console.log(data.name); // реальные данные
}

2. Неправильная обработка ошибок

// ❌ Неправильная обработка ошибок
async function badErrorHandling() {
  const data = await fetch('/api/data').json(); // Ошибка не будет поймана правильно
  return data;
}
 
// ✅ Правильная обработка ошибок
async function goodErrorHandling() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error(`HTTP ошибка: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Ошибка загрузки данных:', error);
    throw error;
  }
}

3. Избыточное использование await

// ❌ Избыточное использование await
async function badChaining() {
  const response = await fetch('/api/data');
  const data = await response.json();
  const processed = await processData(data);
  const validated = await validateData(processed);
  return validated;
}
 
// ✅ Цепочка без лишних await
async function goodChaining() {
  const response = await fetch('/api/data');
  const data = await response.json();
  return validateData(processData(data));
}

Лучшие практики

  1. Всегда используйте try/catch — для обработки ошибок в асинхронных функциях
  2. Используйте Promise.all для параллельных операций — для улучшения производительности
  3. Не забывайте await — особенно перед методами, возвращающими промисы
  4. Обрабатывайте HTTP ошибки — проверяйте response.ok перед вызовом .json()
  5. Избегайте избыточного await — не используйте await там, где это не нужно

Сравнение с промисами

ОсобенностьПромисыAsync/Await
ЧитаемостьЦепочки .then()Линейный код
Обработка ошибок.catch()try/catch
ОтладкаСложнее из-за цепочекПроще, сохраняется стек
ПроизводительностьОдинаковаяОдинаковая
СовместимостьВсе современные браузерыТребует транспиляцию для старых браузеров

Ключевые преимущества async/await

  1. Читаемость — код выглядит как синхронный
  2. Упрощенная отладка — сохраняется стек вызовов
  3. Удобная обработка ошибок — использование привычного try/catch
  4. Легкость обучения — проще для разработчиков, привыкших к синхронному коду
  5. Совместимость — работает с любыми промисами

Async/await — это мощный инструмент, который делает асинхронный код более читаемым и поддерживаемым. Понимание async/await критически важно для современной разработки на JavaScript.


Задача для проверки знаний

Задача

Что будет выведено в консоль и почему? Исправьте код, если необходимо:

async function processData() {
  console.log('1. Начало функции');
  
  const data1 = await fetch('/api/data1');
  console.log('2. После первого запроса');
  
  const data2 = fetch('/api/data2');
  console.log('3. После второго запроса');
  
  const result1 = data1.json();
  console.log('4. После парсинга первого ответа');
  
  const result2 = await data2.json();
  console.log('5. После парсинга второго ответа');
  
  return { result1, result2 };
}
 
processData()
  .then(results => console.log('6. Результаты:', results))
  .catch(error => console.error('7. Ошибка:', error.message));
Посмотреть ответ

Ответ: Код содержит несколько ошибок. Правильная версия:

async function processData() {
  console.log('1. Начало функции');
  
  const response1 = await fetch('/api/data1');
  console.log('2. После первого запроса');
  
  const response2 = await fetch('/api/data2'); // ДОБАВЛЕН await
  console.log('3. После второго запроса');
  
  const result1 = await response1.json();
  console.log('4. После парсинга первого ответа');
  
  const result2 = await response2.json();
  console.log('5. После парсинга второго ответа');
  
  return { result1, result2 };
}

Объяснение ошибок:

  1. Отсутствует await для response1.json() — без await метод .json() возвращает промис, а не данные
  2. Непоследовательное использование await — для response2 await используется правильно, но для response1 забыт

Порядок выполнения:

  1. Выведет “1. Начало функции”
  2. Выполнит fetch(‘/api/data1’) и приостановится до его завершения
  3. Выведет “2. После первого запроса”
  4. Запустит fetch(‘/api/data2’) без ожидания
  5. Выведет “3. После второго запроса”
  6. Выполнит response1.json() и приостановится до завершения
  7. Выведет “4. После парсинга первого ответа”
  8. Дождется завершения response2.json()
  9. Выведет “5. После парсинга второго ответа”
  10. Вернет результаты
  11. Выведет “6. Результаты: [object Object]”

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


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪