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-модуль.
await должен находиться в подходящем контексте
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() вызов запускает работу, но не определяет политику ошибки. Обёртка также не поможет, если результат нужен синхронному вызывающему коду: асинхронное значение всё равно появится позже.
Проверка перед исправлением короткая:
- Найдите непосредственную функцию, которая содержит
await. - Решите, должен ли её вызов возвращать
Promise, и обновите вызывающий код. - Для callback выберите ожидаемую модель: параллельный
Promise.all()или последовательныйfor…of. - Для top-level
awaitубедитесь, что среда загружает файл как ESM.
Ошибка исчезает, когда await оказывается в настоящей асинхронной границе. Само добавление async, модуля или обёртки ещё не гарантирует правильный порядок выполнения и обработку отказов.