W3docs

JavaScript async/await

Изучите JavaScript async/await: async-функции, await, обработка ошибок через try/catch, последовательное и параллельное выполнение с Promise.all и типичные ошибки.

Синтаксис async/await — это современный и удобочитаемый способ работы с асинхронным кодом в JavaScript. Он построен непосредственно поверх промисовasync/await не заменяет их, а предоставляет более чистый синтаксис для их использования. Вместо цепочек колбэков .then() вы пишете код, который читается сверху вниз, как обычный синхронный код, пока движок обрабатывает ожидание за кулисами.

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

async-функции всегда возвращают промис

Пометка функции как async делает две вещи: позволяет использовать await внутри тела функции и гарантирует, что функция возвращает промис. Любое значение, которое вы return-ите, становится значением выполненного промиса; если вы бросаете исключение через throw, промис отклоняется.

javascript— editable

Поскольку возвращаемое значение является промисом, вызывающий код всё равно должен использовать await (или .then()), чтобы получить фактическое значение. Распространённая ошибка новичков — ожидание, что greet() напрямую вернёт 'Hello'.

await приостанавливает выполнение до завершения промиса

Оператор await можно использовать только внутри async-функции (или на верхнем уровне модуля — см. ниже). Он приостанавливает функцию до тех пор, пока промис справа от него не завершится: при выполнении возвращает разрешённое значение, при отклонении — бросает причину отклонения как исключение.

Важно: await не блокирует всю программу. Он приостанавливает только текущую async-функцию; остальной код и цикл событий продолжают работать.

javascript— editable

Можно await-ить любое значение, не только промис. Значения, не являющиеся промисами, оборачиваются и немедленно разрешаются, поэтому await 5 просто возвращает 5.

Обработка ошибок с помощью try...catch

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

javascript— editable

Если вы не перехватываете отклонение, оно становится необработанным отклонением промиса. Подробнее о механике на стороне промисов читайте в разделе обработка ошибок с промисами.

Пример из реального мира: получение данных и корректное отображение ошибок.

javascript— editable

Обратите внимание на явную проверку response.ok: fetch отклоняется только при сетевых ошибках, но не при HTTP-статусах вроде 404 или 500, поэтому вы должны самостоятельно проверять ответ.

Последовательное и параллельное выполнение

Именно здесь await чаще всего используется неправильно. Когда вы await-ите операции одну за другой, они выполняются последовательно — каждая ждёт завершения предыдущей. Это правильно только тогда, когда последующая задача зависит от результата предыдущей.

Последовательно (когда задачи зависят друг от друга)

async function pipeline() {
  const user = await getUser(1);          // step 1
  const posts = await getPosts(user.id);  // needs user.id, so must wait
  return posts;
}

Параллельно (когда задачи независимы)

Если задачи не зависят друг от друга, ожидание их по очереди — пустая трата времени. Запустите их все, а затем дождитесь вместе с помощью Promise.all:

javascript— editable

Promise.all отклоняется, как только один из его промисов отклоняется. Если вместо этого вы хотите дождаться каждого промиса независимо от успеха или неудачи, используйте Promise.allSettled. Полное семейство комбинаторов описано в разделе Promise API.

Await на верхнем уровне в модулях

Внутри ES-модулей (<script type="module"> или файлов .mjs) можно использовать await на верхнем уровне, не оборачивая его в async-функцию:

// data.mjs — an ES module
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const todo = await response.json();

export { todo };

Это удобно для инициализации модуля, например при загрузке конфигурации до того, как экспорты модуля будут готовы. Учтите, что модуль, использующий await на верхнем уровне, задерживает вычисление любого модуля, который его импортирует. await на верхнем уровне работает только в модулях — использование его в обычном скрипте или обычной функции является синтаксической ошибкой.

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

Не используйте await в цикле для независимых задач

Использование await внутри цикла for заставляет итерации выполняться по одной. Если итерации независимы, это неоправданно медленно.

javascript— editable

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

Другие подводные камни

  • forEach игнорирует async-колбэки. array.forEach(async ...) не ждёт завершения промисов. Используйте цикл for...of или Promise.all(array.map(...)).
  • Не забывайте await. Вызов async-функции без await (или .then()) возвращает ожидающий промис и молча проглатывает ошибки. Линтеры часто предупреждают о «висящих» промисах.
  • fetch не отклоняется при HTTP-ошибках. Всегда проверяйте response.ok, как показано выше.

Заключение

async/await делает асинхронный JavaScript читаемым как синхронный код, оставляя цикл событий свободным. Запомните главное: каждая async-функция возвращает промис, await приостанавливает только текущую функцию, ошибки обрабатываются через try...catch, а независимые задачи следует выполнять параллельно с помощью Promise.all, а не ожидать их по одной. Чтобы укрепить основу под этим синтаксисом, изучите разделы промисы и цепочки промисов.

Практика

Практика
Какова функция ключевого слова 'async' в JavaScript?
Какова функция ключевого слова 'async' в JavaScript?
Was this page helpful?