← JavaScript/Async/Await#85 из 383← ПредыдущийСледующий →+35 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Async/Await

Какую проблему решает async/await

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

Сравни: загрузка данных для страницы заказа на Ozon.

С промисами:

getOrder(id)
  .then(order => getProduct(order.productId))
  .then(product => getReviews(product.id))
  .then(reviews => render(product, reviews))
  .catch(e => showError(e))

С async/await:

async function loadOrderPage(id) {
  const order = await getOrder(id)
  const product = await getProduct(order.productId)
  const reviews = await getReviews(product.id)
  render(product, reviews)
}

Тот же код, та же логика — но читается как синхронный алгоритм.

На основе предыдущих уроков

  • «Промисы» — async/await это синтаксический сахар над промисами, они совместимы
  • «try/catch» — для обработки ошибок в async-функциях
  • «Функции» — async-функция это обычная функция с особым поведением
  • async function

    Ключевое слово async делает функцию асинхронной. Такая функция всегда возвращает Promise:

    async function getStatus() {
      return 'OK'  // автоматически: Promise.resolve('OK')
    }
    
    getStatus().then(console.log)  // 'OK'
    
    // Стрелочная async-функция:
    const loadData = async (url) => {
      // ...
    }

    await

    await приостанавливает выполнение async-функции до разрешения промиса. Остальной код продолжает работать — только эта функция «ждёт»:

    async function loadUserProfile(userId) {
      const user = await fetchUser(userId)         // ждём пользователя
      const avatar = await fetchAvatar(user.id)    // ждём аватар
      return { ...user, avatar }
    }

    Без await ты получишь промис, а не его значение — самая частая ошибка.

    Обработка ошибок с try/catch

    async function placeOrder(cartItems, paymentMethod) {
      try {
        const order = await createOrder(cartItems)
        const payment = await processPayment(order.id, paymentMethod)
    
        if (!payment.success) {
          throw new Error('Платёж отклонён банком')
        }
    
        await sendConfirmationEmail(order.id)
        return { orderId: order.id, status: 'confirmed' }
    
      } catch (e) {
        console.error('Ошибка оформления заказа:', e.message)
        await cancelOrder(order?.id)  // откатываем если нужно
        throw e  // пробрасываем для обработки выше
      }
    }

    Параллельное выполнение с Promise.all

    Последовательные await — это медленно, если запросы независимы:

    // Медленно: 200 + 300 + 150 = 650мс
    const user    = await fetchUser(id)       // 200мс
    const orders  = await fetchOrders(id)     // 300мс
    const reviews = await fetchReviews(id)    // 150мс
    
    // Быстро: max(200, 300, 150) = 300мс
    const [user, orders, reviews] = await Promise.all([
      fetchUser(id),
      fetchOrders(id),
      fetchReviews(id),
    ])

    Правило: если запросы независимы — используй Promise.all. Если второй запрос зависит от результата первого — последовательный await.

    Типичные ошибки

    1. Забыли await — получили промис вместо данных:

    // Сломано:
    async function showUser(id) {
      const user = fetchUser(id)      // нет await — user это Promise, не объект!
      console.log(user.name)          // undefined
    }
    
    // Исправлено:
    async function showUser(id) {
      const user = await fetchUser(id)
      console.log(user.name)          // 'Иван'
    }

    2. await вне async-функции:

    // Сломано — SyntaxError:
    function loadData() {
      const data = await fetch('/api')  // SyntaxError: await вне async
    }
    
    // Исправлено:
    async function loadData() {
      const data = await fetch('/api')
    }

    3. Последовательные await для независимых запросов:

    // Медленно — ждём каждый по очереди (650мс):
    async function loadDashboard(id) {
      const stats    = await fetchStats(id)    // 200мс — ждём
      const activity = await fetchActivity(id) // 300мс — ждём снова
      const alerts   = await fetchAlerts(id)   // 150мс — ждём снова
      return { stats, activity, alerts }
    }
    
    // Быстро — параллельно (300мс):
    async function loadDashboard(id) {
      const [stats, activity, alerts] = await Promise.all([
        fetchStats(id),
        fetchActivity(id),
        fetchAlerts(id),
      ])
      return { stats, activity, alerts }
    }

    В реальных проектах

  • React: useEffect(() => { async function load() {...} load() }, []) — стандартный паттерн загрузки
  • Next.js: async function getServerSideProps() — серверный рендеринг
  • Express.js: app.get('/users', async (req, res) => { const users = await db.find() })
  • Везде где есть I/O: чтение файлов, запросы к БД, HTTP-запросы
  • Примеры

    Загрузка страницы заказа: последовательно vs параллельно, обработка ошибок

    const delay = ms => new Promise(r => setTimeout(r, ms))
    
    // Симуляция API интернет-магазина
    async function fetchOrder(orderId) {
      await delay(100)
      if (orderId <= 0) throw new Error('Неверный ID заказа')
      return { id: orderId, productId: 42, quantity: 2, status: 'processing' }
    }
    
    async function fetchProduct(productId) {
      await delay(150)
      return { id: productId, name: 'MacBook Pro', price: 180000 }
    }
    
    async function fetchUser(userId) {
      await delay(80)
      return { id: userId, name: 'Иван Петров', email: 'ivan@mail.ru' }
    }
    
    async function fetchShippingAddress(userId) {
      await delay(60)
      return { city: 'Москва', street: 'Ленина 42', zip: '101000' }
    }
    
    // Страница заказа: продукт зависит от заказа (последовательно),
    // пользователь и адрес независимы (параллельно)
    async function loadOrderPage(orderId, userId) {
      try {
        // 1. Сначала получаем заказ (100мс)
        const order = await fetchOrder(orderId)
        console.log('Заказ загружен:', order.id)
    
        // 2. Параллельно: продукт (зависит от заказа) + пользователь + адрес
        const [product, user, address] = await Promise.all([
          fetchProduct(order.productId),    // 150мс
          fetchUser(userId),                 // 80мс
          fetchShippingAddress(userId),      // 60мс
        ])
        // Итого: 100мс (заказ) + 150мс (самый медленный параллельный) = 250мс
    
        console.log('Товар:', product.name, '—', product.price.toLocaleString('ru-RU') + ' ₽')
        console.log('Покупатель:', user.name)
        console.log('Адрес:', address.city + ', ' + address.street)
    
        const total = product.price * order.quantity
        console.log('Итого:', total.toLocaleString('ru-RU') + ' ₽')
    
        return { order, product, user, address, total }
    
      } catch (e) {
        console.error('Ошибка загрузки страницы заказа:', e.message)
        return null
      }
    }
    
    loadOrderPage(1001, 7)
    
    // Тест ошибки
    loadOrderPage(-1, 7)

    Async/Await

    Какую проблему решает async/await

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

    Сравни: загрузка данных для страницы заказа на Ozon.

    С промисами:

    getOrder(id)
      .then(order => getProduct(order.productId))
      .then(product => getReviews(product.id))
      .then(reviews => render(product, reviews))
      .catch(e => showError(e))

    С async/await:

    async function loadOrderPage(id) {
      const order = await getOrder(id)
      const product = await getProduct(order.productId)
      const reviews = await getReviews(product.id)
      render(product, reviews)
    }

    Тот же код, та же логика — но читается как синхронный алгоритм.

    На основе предыдущих уроков

  • «Промисы» — async/await это синтаксический сахар над промисами, они совместимы
  • «try/catch» — для обработки ошибок в async-функциях
  • «Функции» — async-функция это обычная функция с особым поведением
  • async function

    Ключевое слово async делает функцию асинхронной. Такая функция всегда возвращает Promise:

    async function getStatus() {
      return 'OK'  // автоматически: Promise.resolve('OK')
    }
    
    getStatus().then(console.log)  // 'OK'
    
    // Стрелочная async-функция:
    const loadData = async (url) => {
      // ...
    }

    await

    await приостанавливает выполнение async-функции до разрешения промиса. Остальной код продолжает работать — только эта функция «ждёт»:

    async function loadUserProfile(userId) {
      const user = await fetchUser(userId)         // ждём пользователя
      const avatar = await fetchAvatar(user.id)    // ждём аватар
      return { ...user, avatar }
    }

    Без await ты получишь промис, а не его значение — самая частая ошибка.

    Обработка ошибок с try/catch

    async function placeOrder(cartItems, paymentMethod) {
      try {
        const order = await createOrder(cartItems)
        const payment = await processPayment(order.id, paymentMethod)
    
        if (!payment.success) {
          throw new Error('Платёж отклонён банком')
        }
    
        await sendConfirmationEmail(order.id)
        return { orderId: order.id, status: 'confirmed' }
    
      } catch (e) {
        console.error('Ошибка оформления заказа:', e.message)
        await cancelOrder(order?.id)  // откатываем если нужно
        throw e  // пробрасываем для обработки выше
      }
    }

    Параллельное выполнение с Promise.all

    Последовательные await — это медленно, если запросы независимы:

    // Медленно: 200 + 300 + 150 = 650мс
    const user    = await fetchUser(id)       // 200мс
    const orders  = await fetchOrders(id)     // 300мс
    const reviews = await fetchReviews(id)    // 150мс
    
    // Быстро: max(200, 300, 150) = 300мс
    const [user, orders, reviews] = await Promise.all([
      fetchUser(id),
      fetchOrders(id),
      fetchReviews(id),
    ])

    Правило: если запросы независимы — используй Promise.all. Если второй запрос зависит от результата первого — последовательный await.

    Типичные ошибки

    1. Забыли await — получили промис вместо данных:

    // Сломано:
    async function showUser(id) {
      const user = fetchUser(id)      // нет await — user это Promise, не объект!
      console.log(user.name)          // undefined
    }
    
    // Исправлено:
    async function showUser(id) {
      const user = await fetchUser(id)
      console.log(user.name)          // 'Иван'
    }

    2. await вне async-функции:

    // Сломано — SyntaxError:
    function loadData() {
      const data = await fetch('/api')  // SyntaxError: await вне async
    }
    
    // Исправлено:
    async function loadData() {
      const data = await fetch('/api')
    }

    3. Последовательные await для независимых запросов:

    // Медленно — ждём каждый по очереди (650мс):
    async function loadDashboard(id) {
      const stats    = await fetchStats(id)    // 200мс — ждём
      const activity = await fetchActivity(id) // 300мс — ждём снова
      const alerts   = await fetchAlerts(id)   // 150мс — ждём снова
      return { stats, activity, alerts }
    }
    
    // Быстро — параллельно (300мс):
    async function loadDashboard(id) {
      const [stats, activity, alerts] = await Promise.all([
        fetchStats(id),
        fetchActivity(id),
        fetchAlerts(id),
      ])
      return { stats, activity, alerts }
    }

    В реальных проектах

  • React: useEffect(() => { async function load() {...} load() }, []) — стандартный паттерн загрузки
  • Next.js: async function getServerSideProps() — серверный рендеринг
  • Express.js: app.get('/users', async (req, res) => { const users = await db.find() })
  • Везде где есть I/O: чтение файлов, запросы к БД, HTTP-запросы
  • Примеры

    Загрузка страницы заказа: последовательно vs параллельно, обработка ошибок

    const delay = ms => new Promise(r => setTimeout(r, ms))
    
    // Симуляция API интернет-магазина
    async function fetchOrder(orderId) {
      await delay(100)
      if (orderId <= 0) throw new Error('Неверный ID заказа')
      return { id: orderId, productId: 42, quantity: 2, status: 'processing' }
    }
    
    async function fetchProduct(productId) {
      await delay(150)
      return { id: productId, name: 'MacBook Pro', price: 180000 }
    }
    
    async function fetchUser(userId) {
      await delay(80)
      return { id: userId, name: 'Иван Петров', email: 'ivan@mail.ru' }
    }
    
    async function fetchShippingAddress(userId) {
      await delay(60)
      return { city: 'Москва', street: 'Ленина 42', zip: '101000' }
    }
    
    // Страница заказа: продукт зависит от заказа (последовательно),
    // пользователь и адрес независимы (параллельно)
    async function loadOrderPage(orderId, userId) {
      try {
        // 1. Сначала получаем заказ (100мс)
        const order = await fetchOrder(orderId)
        console.log('Заказ загружен:', order.id)
    
        // 2. Параллельно: продукт (зависит от заказа) + пользователь + адрес
        const [product, user, address] = await Promise.all([
          fetchProduct(order.productId),    // 150мс
          fetchUser(userId),                 // 80мс
          fetchShippingAddress(userId),      // 60мс
        ])
        // Итого: 100мс (заказ) + 150мс (самый медленный параллельный) = 250мс
    
        console.log('Товар:', product.name, '—', product.price.toLocaleString('ru-RU') + ' ₽')
        console.log('Покупатель:', user.name)
        console.log('Адрес:', address.city + ', ' + address.street)
    
        const total = product.price * order.quantity
        console.log('Итого:', total.toLocaleString('ru-RU') + ' ₽')
    
        return { order, product, user, address, total }
    
      } catch (e) {
        console.error('Ошибка загрузки страницы заказа:', e.message)
        return null
      }
    }
    
    loadOrderPage(1001, 7)
    
    // Тест ошибки
    loadOrderPage(-1, 7)

    Задание

    Ты разрабатываешь систему оформления заказа в интернет-магазине. Напиши `async` функцию `checkout(cartId, paymentInfo)`, которая последовательно: 1. `await validateCart(cartId)` — получает корзину с товарами 2. `await calculateTotal(cart)` — считает итоговую сумму 3. `await processPayment(total, paymentInfo)` — проводит оплату 4. Выводит результат каждого шага Вспомогательные функции уже написаны. Обработай ошибки в `try/catch`.

    Подсказка

    const cart = await validateCart(cartId). Затем const { total, itemCount } = await calculateTotal(cart). Затем const payment = await processPayment(total, paymentInfo). Весь блок в try/catch.

    Загружаем среду выполнения...
    Загружаем AI-помощника...