Функция async в TypeScript всегда возвращает промис: если её тело возвращает string, её тип Promise<string>. Внутри неё await разворачивает Promise<T> в T.
Поведение во время выполнения (что приостанавливает await, цикл событий) это JavaScript, описанный в разделе async/await в JavaScript. Часть TypeScript это типы на входе и выходе.
Типы возврата асинхронных функций
Объявленный тип возврата функции async должен быть Promise<...>, хотя тело возвращает обычное значение. Если его не указать, TypeScript выведет его сам.
Запись async function count(): number это ошибка TS1064 (The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?). Служебный тип Awaited<T> даёт развёрнутый тип: Awaited<ReturnType<typeof count>> это number.
Забытый await
Пропущенный await оставляет у вас Promise<T> вместо T. TypeScript ловит большинство таких случаев, потому что типы перестают совпадать:
Ошибка с if (ok) настоящая: объект промиса всегда истинен, поэтому доступ был предоставлен. TypeScript сообщает о ней как TS2801. Присваивание промиса переменной boolean или чтение свойства, которого у промиса нет, тоже не скомпилировалось бы. Вызов, результат которого игнорируется (save(user);), не ловится; для этого есть правило typescript-eslint no-floating-promises.
Обработка ошибок через try/catch
Отклонённый промис заставляет await выбросить ошибку, поэтому обычный try/catch работает. При strict переменная в catch имеет тип unknown, и её нужно сузить, прежде чем читать .message:
throw внутри функции async отклоняет её промис, а не выбрасывает ошибку в месте вызова. Больше приёмов, включая собственные классы ошибок и возврат результатов вместо выбрасывания, есть на странице обработка ошибок.
await верхнего уровня
await вне любой функции работает только в модуле ES. Файл, скомпилированный как CommonJS (так обстоит дело с этими примерами и с проектами Node без "type": "module"), его отвергает:
index.ts(2,14): error TS1309: The current file is a CommonJS module and cannot use 'await' at the top level.
Оберните код в async-функцию main и вызовите её, как делает каждый пример на этой странице. В проекте на модулях ES ("type": "module" в package.json с module, равным node16 или nodenext, или module: "esnext" для бандлера) await верхнего уровня разрешён.
По очереди и параллельно
Каждый await ждёт свой промис, прежде чем начнётся следующая строка. Для независимых вызовов сначала запустите их все, а затем дождитесь вместе через Promise.all:
В последовательной половине b не может завершиться раньше a, потому что ещё не запущен. В параллельной половине первым завершается d, а общее время примерно равно самому долгому вызову, а не сумме. Promise.all всё равно возвращает результаты в порядке входов, типизированные как кортеж.
Ловушка forEach
forEach игнорирует промис, который возвращает async-колбэк, поэтому работу никто не ждёт:
for...of с await обрабатывает элементы по одному; Promise.all с map запускает их параллельно и ждёт все. forEach не делает ни того, ни другого, и TypeScript не предупреждает, потому что колбэк с типом возврата void принимает и тот, что возвращает промис.
Асинхронная итерация через for await
for await...of перебирает асинхронный итерируемый объект, например асинхронный генератор, дожидаясь каждого значения:
async function* pages(total: number): AsyncGenerator<string[]> {
for (let page = 1; page <= total; page++) {
await new Promise((r) => setTimeout(r, 10));
yield [`item ${page}a`, `item ${page}b`];
}
}
async function main() {
for await (const batch of pages(3)) {
console.log(batch.join(", ")); // batch: string[]
}
}
main();
Тип элемента берётся из аннотации генератора AsyncGenerator<T> или выводится, если её не указать.
Часто задаваемые вопросы
Какой тип возврата у async-функции в TypeScript?
Всегда промис. Async-функция, которая возвращает number, имеет тип возврата Promise<number>, а та, что ничего не возвращает, Promise<void>. Запись async function f(): number это ошибка TS1064, которая предлагает Promise<number>.
Как использовать await на верхнем уровне в TypeScript?
await верхнего уровня работает только в модуле ES, когда module равен es2022, esnext, system, preserve или node16/node18/node20/nodenext в файле, который Node считает ESM, а target не ниже es2017. В файле CommonJS это ошибка TS1309. Переносимое решение это async-функция main: async function main() { ... } main();.
Как обрабатывать ошибки с async/await в TypeScript?
Оберните await в try/catch. При strict пойманное значение имеет тип unknown, поэтому сначала сузьте его: if (e instanceof Error) console.log(e.message). Отклонение, которое никто не ждёт и не ловит, становится необработанным.
Как запускать асинхронные вызовы параллельно в TypeScript?
Сначала запустите все промисы, затем дождитесь их вместе: const [a, b] = await Promise.all([loadA(), loadB()]). Запись await loadA(); await loadB(); выполняет их один за другим. Promise.all сохраняет тип каждого результата в итоговом кортеже.
Почему async не работает внутри forEach?
forEach вызывает колбэк и игнорирует то, что он возвращает, поэтому промисы из async-колбэка никто не ждёт: цикл завершается сразу, и код после него выполняется до окончания работы. Используйте for...of с await для работы по очереди или await Promise.all(items.map(async (x) => ...)) для параллельной работы.