SyntaxError: await is only valid in async functions — как исправить

JavaScript Автор: Среда и версия: Современные браузеры и Node.js с поддержкой ECMAScript modules
содержание

SyntaxError: await is only valid in async functions and the top level bodies of modules означает, что парсер встретил await в синхронном контексте. Код ещё не начал выполняться, поэтому try…catch вокруг вызова, проверка сети или замена fetch() не исправят ошибку.

Текст сообщения зависит от движка. Firefox упоминает async-функции, async-генераторы и модули, Safari может написать Unexpected identifier. Во всех случаях сначала найдите ближайшую функцию вокруг await, а если её нет — выясните, загружен ли файл как ECMAScript-модуль.

JavaScript / 01

await должен находиться в подходящем контексте

await должен находиться в подходящем контексте01 / вход 02 / операция 03 / результат тело функции async function вернёт Promise callback async callback дождись результатов верхний уровень ES-модуль .mjs / type=module01 / вход02 / операция03 / результаттело функцииasync functionвернёт Promisecallbackasync callbackдождись результатовверхний уровеньES-модуль.mjs / type=module
Допустимость await задаёт его непосредственный синтаксический контекст: async у внешней функции не распространяется на вложенный callback, а верхний уровень требует режима модуля.

Если await находится в функции

Пометьте async ту функцию, которая непосредственно содержит await:

async function loadProfile() {
  const response = await fetch('/api/profile')

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }

  return response.json()
}

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

async function getAnswer() {
  return 42
}

async function showAnswer() {
  const answer = await getAnswer()
  console.log(answer)
}

showAnswer()

Это изменение контракта функции. Код, который раньше ожидал число, строку или объект сразу, теперь должен дождаться промиса либо продолжить цепочку через .then(). Перед добавлением async проверьте все места вызова и тип возвращаемого значения.

Не обязательно протягивать async до самого верха приложения. Если вызывающая функция уже умеет возвращать промис, его можно вернуть напрямую:

function loadProfile() {
  return fetch('/api/profile').then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }

    return response.json()
  })
}

Ошибки самого запроса, CORS и HTTP-статусы появляются уже после успешного разбора программы. Их диагностика описана отдельно в статье TypeError: Failed to fetch.

Если await находится внутри callback

Вложенная функция создаёт собственный синтаксический контекст. Поэтому async у внешней функции не разрешает await внутри обычного callback:

async function loadUsers(ids) {
  return ids.map((id) => {
    const response = await fetch(`/api/users/${id}`)
    return response.json()
  })
}

Если запросы независимы и нужны все результаты, сделайте асинхронным сам callback и соберите созданные им промисы через Promise.all():

async function loadUsers(ids) {
  return Promise.all(
    ids.map(async (id) => {
      const response = await fetch(`/api/users/${id}`)

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`)
      }

      return response.json()
    }),
  )
}

map() вернёт массив промисов, а Promise.all() — один промис со всеми результатами в исходном порядке. Если хотя бы один промис отклонится, Promise.all() тоже отклонится. Для задачи, где нужно сохранить и успешные, и неуспешные исходы, подходит Promise.allSettled().

Не заменяйте map() на forEach(async () => …), если вызывающий код должен дождаться завершения. forEach() игнорирует возвращаемые значения callback и сам возвращает undefined, поэтому ожидать его нечего.

Когда следующий шаг зависит от предыдущего, используйте обычный цикл:

async function sendInOrder(messages) {
  for (const message of messages) {
    await sendMessage(message)
  }
}

Такой цикл выполняет операции последовательно. Для независимых запросов это может быть лишним ожиданием: Promise.all(messages.map(sendMessage)) запускает их без искусственной очереди. Выбирайте порядок по зависимостям и ограничениям API, а не только ради устранения SyntaxError.

Если await написан на верхнем уровне

Top-level await разрешён только в ECMAScript-модулях. В обычном браузерном скрипте этот код синтаксически неверен:

<script>
  const response = await fetch('/api/config')
</script>

Если файл действительно должен быть модулем, укажите это при загрузке:

<script type="module" src="/app.js"></script>

Теперь await допустим на верхнем уровне app.js. Но смена режима затрагивает не только одну строку: у модулей своя область видимости, строгий режим и правила import/export. Не добавляйте type="module" вслепую в старый скрипт, который рассчитывает на глобальные переменные или порядок обычных <script>.

В Node.js явные варианты для ECMAScript-модуля — расширение .mjs или ближайший package.json с полем "type": "module" для файлов .js:

{
  "type": "module"
}

Для кода из --eval или стандартного ввода доступен флаг --input-type=module. Файл .cjs остаётся CommonJS-модулем, поэтому top-level await в нём недопустим. Если проект переходит с CommonJS на ESM, сначала проверьте импорты и экспорты всего затронутого пакета; это отдельное архитектурное изменение, а не локальная правка синтаксиса.

Почему обёртка помогает не всегда

Асинхронная функция-обёртка делает await синтаксически допустимым:

async function main() {
  const config = await loadConfig()
  startApp(config)
}

main().catch((error) => {
  console.error(error)
})

Именованная main() уместна как настоящая точка запуска: видно, кто получает её промис и где обрабатывается отказ. Без .catch() вызов запускает работу, но не определяет политику ошибки. Обёртка также не поможет, если результат нужен синхронному вызывающему коду: асинхронное значение всё равно появится позже.

Проверка перед исправлением короткая:

  1. Найдите непосредственную функцию, которая содержит await.
  2. Решите, должен ли её вызов возвращать Promise, и обновите вызывающий код.
  3. Для callback выберите ожидаемую модель: параллельный Promise.all() или последовательный for…of.
  4. Для top-level await убедитесь, что среда загружает файл как ESM.

Ошибка исчезает, когда await оказывается в настоящей асинхронной границе. Само добавление async, модуля или обёртки ещё не гарантирует правильный порядок выполнения и обработку отказов.

Источники