Клавиша / esc

async/await

Ждать выполнения асинхронного кода стало легче! Больше никаких колбэков и промисов, только новые ключевые слова.

Время чтения: меньше 5 мин

Эта документация связана с понятием асинхронности в JavaScript. Зачем нужен асинхронный код и как он работает в деталях описано в обзорной статье «Асинхронность в JavaScript».

Кратко

Скопировано

Добавленное перед определением функции ключевое слово async делает функцию асинхронной. Возвращаемое значение такой функции автоматически оборачивается в Promise:

        
          
          async function getStarWarsMovies() {  return 1}console.log(getStarWarsMovies())// Promise { <state>: "fulfilled", <value>: 1 }
          async function getStarWarsMovies() {
  return 1
}

console.log(getStarWarsMovies())
// Promise { <state>: "fulfilled", <value>: 1 }

        
        
          
        
      

Асинхронные функции нужны для выполнения асинхронных операций: работой с API, базами данных, чтения файлов и т. д.

Асинхронные операции выполняются не сразу: код отправил запрос к API и ждёт, пока сервер пришлёт ответ. Ключевое слово await используется, чтобы дождаться выполнения асинхронной операции:

        
          
          async function getStarWarsMovie(id) {  const response = await fetch(`https://swapi.dev/api/films/${id}/`)  console.log('ответ получен', response)  // *1  return response.json()}const movies = getStarWarsMovie(1).then((movie) => {  console.log(movie.title)  // *2})console.log('результат вызова функции', movies)// *3
          async function getStarWarsMovie(id) {
  const response = await fetch(`https://swapi.dev/api/films/${id}/`)
  console.log('ответ получен', response)
  // *1

  return response.json()
}

const movies = getStarWarsMovie(1).then((movie) => {
  console.log(movie.title)
  // *2
})

console.log('результат вызова функции', movies)
// *3

        
        
          
        
      

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

Вывод на экран будет следующий:

        
          
          // Вызвали функцию, она начала выполнять// асинхронную операцию и вернула промис (*3)'результат вызова функции' Promise// Получили ответ API, продолжаем// выполнение функции (*1)'ответ получен' Response// Сработал колбэк (*2)'A New Hope'
          // Вызвали функцию, она начала выполнять
// асинхронную операцию и вернула промис (*3)
'результат вызова функции' Promise

// Получили ответ API, продолжаем
// выполнение функции (*1)
'ответ получен' Response

// Сработал колбэк (*2)
'A New Hope'

        
        
          
        
      

Как понять

Скопировано

Ключевые слова async/await не привносят в JavaScript что-то новое. Они только упрощают работу с промисами.

Вместо кода с цепочкой вызовов:

        
          
          function getMainActorProfileFromMovie(id) {  return fetch(`https://swapi.dev/api/films/${id}/`)    .then((movieResponse) => {      return movieResponse.json()    })    .then((movie) => {      const characterUrl = movie.characters[0].split('//')[1]      return fetch(`https://${characterUrl}`)    })    .then((characterResponse) => {      return characterResponse.json()    })    .catch((err) => {      console.error('Произошла ошибка!', err)    })}getMainActorProfileFromMovie(1).then((profile) => {  console.log(profile)})
          function getMainActorProfileFromMovie(id) {
  return fetch(`https://swapi.dev/api/films/${id}/`)
    .then((movieResponse) => {
      return movieResponse.json()
    })
    .then((movie) => {
      const characterUrl = movie.characters[0].split('//')[1]
      return fetch(`https://${characterUrl}`)
    })
    .then((characterResponse) => {
      return characterResponse.json()
    })
    .catch((err) => {
      console.error('Произошла ошибка!', err)
    })
}

getMainActorProfileFromMovie(1).then((profile) => {
  console.log(profile)
})

        
        
          
        
      

Можно записать с async/await:

        
          
          async function getMainActorProfileFromMovie(id) {  try {    const movieResponse = await fetch(`https://swapi.dev/api/films/${id}/`)    const movie = await movieResponse.json()    const characterUrl = movie.characters[0].split('//')[1]    const characterResponse = await fetch(`https://${characterUrl}`)    return characterResponse.json()  } catch (err) {    console.error('Произошла ошибка!', err)  }}getMainActorProfileFromMovie(1).then(  (profile) => {console.log(profile)})
          async function getMainActorProfileFromMovie(id) {
  try {
    const movieResponse = await fetch(`https://swapi.dev/api/films/${id}/`)
    const movie = await movieResponse.json()

    const characterUrl = movie.characters[0].split('//')[1]
    const characterResponse = await fetch(`https://${characterUrl}`)
    return characterResponse.json()
  } catch (err) {
    console.error('Произошла ошибка!', err)
  }
}

getMainActorProfileFromMovie(1).then(
  (profile) => {console.log(profile)}
)

        
        
          
        
      

Такой код проще понимать:

  • он плоский;
  • выглядит, как синхронный;
  • использует стандартный блок try...catch для обработки ошибок.

☝️ Ключевое слово await может использоваться не только внутри асинхронных функций, но и в модулях.

Подробнее об использовании `await` в модулях (Top level await)

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

Допустим, у нас есть модуль Parent.mjs, импортирующий данные из модулей Child.mjs:

        
          
          // Parent.mjsimport {data} from './Child.mjs'console.log('Parent:', data)
          // Parent.mjs
import {data} from './Child.mjs'

console.log('Parent:', data)

        
        
          
        
      

Модуль Child.mjs экспортирует данные, полученные асинхронно:

        
          
          // Child.mjs// Пример асинхронной функции, возвращающей Promiseconst promise =  fetch('https://dummyjson.com/products/1')  .then(res => res.json())export const data = await promise
          // Child.mjs

// Пример асинхронной функции, возвращающей Promise
const promise =
  fetch('https://dummyjson.com/products/1')
  .then(res => res.json())

export const data = await promise

        
        
          
        
      

При запуске Parent.mjs будет ожидать завершения асинхронной операции.

💡 Возможность использовать await вне асинхронной функции в модулях появилась в стандарте ES2022.

Попытка использовать await вне модуля и не в асинхронной функции приведёт к синтаксической ошибке:
SyntaxError: await is only valid in async functions and the top level bodies of modules.

        
          
          function getMainActorProfileFromMovie(id) {  // Код из примера выше}await getMainActorProfileFromMovie(1)
          function getMainActorProfileFromMovie(id) {
  // Код из примера выше
}

await getMainActorProfileFromMovie(1)

        
        
          
        
      

На практике

Скопировано

Николай Лопин советует

Скопировано

🛠 Всегда используйте async/await вместо цепочек then() и колбэков.

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

🛠 await нельзя использовать вне асинхронных функций. Если нужно выполнить асинхронную операцию в глобальной области видимости, придётся воспользоваться then.

Алексей Орлов советует

Скопировано

🛠 Используйте await Promise.all([...]) для параллельного выполнения нескольких независимых асинхронных функций.

Используя async/await, мы делаем наш код последовательным: ожидаем выполнения одной асинхронной функции и лишь после запускаем другую. В примере ниже новости будут запрошены только после получения пользователя:

        
          
          async function getUser(){  // Возвращает информацию о пользователе}async function getNews(){  // Возвращает список новостей}const user = await getUser()const news = await getNews()
          async function getUser(){
  // Возвращает информацию о пользователе
}

async function getNews(){
  // Возвращает список новостей
}

const user = await getUser()
const news = await getNews()

        
        
          
        
      

Но, запустив getNews параллельно c getUser, мы в большинстве случаев получим результат быстрее. Promise.all() позволяет запустить запросы параллельно, при этом дожидаться результата мы можем как и раньше при помощи await:

        
          
          const [user, news] = await Promise.all([  getUser(),  getNews()])
          const [user, news] = await Promise.all([
  getUser(),
  getNews()
])

        
        
          
        
      

🛠 Не смешивайте синтаксис async/await и Promise.then, старайтесь применять один подход на проекте: так код легче читать и поддерживать.