Fetch API — это инструмент для выполнения сетевых запросов в веб-приложениях. При базовом использовании fetch() — довольно простой метод, но у него есть много нюансов. Например, мы не можем прервать fetch-запрос.
В этой статье рассмотрим сценарии использования fetch() вместе с другой возможностью языка — синтаксисом async/await. Разберемся, как получать данные, обрабатывать ошибки и отменять запросы.

Fetch API позволяет делать HTTP-запросы (GET, POST и т. д.) и обмениваться данными с сервером. Это более удобный аналог XHR.
Чтобы выполнить запрос, просто вызовите функцию fetch():
const response = await fetch(resource[, options]);
- Первый параметр
resource— это URL-адрес запроса и объект Request. - Второй (необязательный) параметр
options— это конфигурация запроса. Можно настроитьmethod,header,body,credentialsи другие опции.
Функция fetch выполняет запрос и возвращает промис, который будет ждать, когда запрос завершится. После этого промис выполняется (resolve) с объектом Response (ответ сервера). Если во время запроса произошла какая-то ошибка, промис переходит в состояние rejected.
Синтаксис async/await прекрасно сочетается с fetch() и помогает упростить работу с промисами. Давайте для примера сделаем запрос списка фильмов:
async function fetchMovies() {
const response = await fetch('/movies');
// ждем выполнения запроса
console.log(response);
}
Функция fetchMovies асинхронная, используем для ее создания ключевое слово async. Внутри она использует await, чтобы дождаться выполнения асинхронной операции fetch.
Внутри функции выполняется запрос на урл /movies. Когда он успешно завершается, мы получаем объект response с ответом сервера. Дальше в статье мы разберемся, как извлечь данные из этого объекта.
Получение JSON
Из объекта response, который возвращается из await fetch() можно извлечь данные в нескольких разных форматах. Чаще всего используется JSON:
async function fetchMoviesJSON() {
const response = await fetch('/movies');
const movies = await response.json();
return movies;
}
fetchMoviesJSON().then(movies => {
movies; // полученный список фильмов
});
Итак, чтобы извлечь полученные данные в виде JSON, нужно использовать метод response.json(). Этот метод возвращает промис, так что придется снова воспользоваться синтаксисом await, чтобы дождаться его выполнения: await response.json().
Кроме того, у объекта Response есть еще несколько полезных методов (все методы возвращают промисы):
response.json()возвращает промис, который резолвится в JSON-объект;response.text()возвращает промис, который резолвится в обычный текст;response.formData()возвращает промис, который резолвится в объект FormData;response.blob()возвращает промис, который резолвится в Blob (файлоподобный объект с необработанными данными);response.arrayBuffer()()возвращает промис, который резолвится в ArrayBuffer (необработанные двоичные данные).
Обработка ошибок
Для разработчиков, которые только начинают работать с fetch, может быть непривычным то, что этот метод не выбрасывает исключение, если сервер возвращает «плохой» HTTP-статус (клиентские 400-499 или серверные 500-599 ошибки).
Попробуем для примера обратиться к несуществующей странице /oops. Этот запрос, как и ожидается, завершается со статусом 404.
async function fetchMovies404() {
const response = await fetch('/oops');
response.ok; // => false
response.status; // => 404
const text = await response.text();
return text;
}
fetchMovies404().then(text => {
text; // => 'Page not found'
});
Из объекта response мы можем узнать, что запрос не удался, однако метод fetch() не выбрасывает ошибку, а считает этот запрос завершенным.
Промис, возвращаемый функцией fetch, отклоняется только в том случае, если запрос не может быть выполнен или ответ не может быть получен, например, из-за проблем с сетью (нет подключения, хост не найден, сервер не отвечает).
Но к счастью, у нас есть поле response.ok, с помощью которого мы можем отловить плохие статусы. Оно принимает значение true только если статус ответа 200-299.
Если вы хотите получать ошибку для всех неудачных запросов, просто выбрасывайте ее вручную:
async function fetchMoviesBadStatus() {
const response = await fetch('/oops');
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
}
const movies = await response.json();
return movies;
}
fetchMoviesBadStatus().catch(error => {
error.message; // 'An error has occurred: 404'
});
Отмена fetch-запроса
К сожалению, Fetch API не предоставляет нам никакой возможности отменить запущенный запрос. Но это можно сделать с помощью AbortController.
Чтобы объединить эти инструменты, нужно сделать 3 действия:
// Шаг 1. Создать экземпляр AbortController до начала запроса
const controller = new AbortController();
// Step 2: Передать в параметры запроса fetch() controller.signal
fetch(..., { signal: controller.signal });
// Step 3: Отменить запрос методом controller.abort при необходимости
controller.abort();
Давайте для примера создадим маленькое приложение с двумя кнопками, одна из которых будет запускать fetch-запрос, а вторая прерывать его.
let controller = null;
// Обрабатываем клики по первой кнопке
fetchMoviesButton.addEventListener('click', async () => {
controller = new AbortController();
try {
const response = await fetch('/movies', {
signal: controller.signal
});
} catch (error) {
console.log('Fetch error: ', error);
}
controller = null;
});
// Обрабатываем клики по второй кнопке
cancelFetchButton.addEventListener('click', () => {
if (controller) {
controller.abort();
}
});
Демо:
Кликните по кнопке Fetch movies, чтобы запустить запрос, а затем по кнопке Cancel fetch, чтобы отменить его. При этом возникнет ошибка, которую поймает блок .catch().
Экземпляр AbortController одноразовый, его нельзя переиспользовать для нескольких запросов. Поэтому для каждого вызова fetch нужно создавать новый инстанс.
- Как прервать fetch-запрос, если он не завершился через определенное время
Параллельные fetch запросы
Чтобы выполнять fetch-запросы параллельно, можно воспользоваться методом Promise.all().
Например, запустим сразу два запроса — для получения фильмов и для получения категорий фильмов:
async function fetchMoviesAndCategories() {
const [moviesResponse, categoriesResponse] = await Promise.all([
fetch('/movies'),
fetch('/categories')
]);
const movies = await moviesResponse.json();
const categories = await categoriesResponse.json();
return [movies, categories];
}
fetchMoviesAndCategories().then(([movies, categories]) => {
movies; // список фильмов
categories; // список категорий
}).catch(error => {
// один из запросов завершился с ошибкой
});
Фрагмент кода await Promise.all([]) запускает запросы параллельно и ожидает, когда все они перейдут в состояние resolved.
Если один из запросов завершится с ошибкой, то Promise.all тоже выбросит ошибку.
Если же вы хотите, чтобы выполнились все запросы, даже если несколько из них упали, используйте метод Promise.allSettled().
Заключение
Вызов функции fetch() запускает запрос к серверу и возвращает промис. Когда запрос успешно завершается, промис переходит в состояние resolved и возвращает объект ответа (response), из которого можно извлечь данные в одном из доступных форматов (JSON, необработанный текст или Blob).
Так как fetch возвращает промис, мы можем использовать синтаксис async/await, чтобы упростить код: response = await fetch().
В статье мы разобрали, как использовать эту комбинацию для получения данных, обработки ошибок, отмены запросов и выполнения параллельных запросов.
I have stripe async code in my React app, and trying to add error handling in my code but have no idea how to handle it. i know how to do it with .then() but async/await is new to me
EDITED
added .catch() i got errors in network tab in response tab.
but i can log it to console?
submit = async () => {
const { email, price, name, phone, city, street, country } = this.state;
let { token } = await this.props.stripe
.createToken({
name,
address_city: city,
address_line1: street,
address_country: country
})
.catch(err => {
console.log(err.response.data);
});
const data = {
token: token.id,
email,
price,
name,
phone,
city,
street,
country
};
let response = await fetch("/charge/pay", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).catch(err => {
console.log(err.response.data);
});
console.log(response);
if (response.ok)
this.setState({
complete: true
});
};
thanks
- Назад
- Обзор: Asynchronous
- Далее (en-US)
В ECMAScript версии 2017 появились async functions и ключевое слово await (ECMAScript Next support in Mozilla). По существу, такие функции есть синтаксический сахар над Promises и Generator functions (ts39). С их помощью легче писать/читать асинхронный код, ведь они позволяют использовать привычный синхронный стиль написания. В этой статье мы на базовом уровне разберёмся в их устройстве.
| Примечания: | Чтобы лучше понять материал, желательно перед чтением ознакомиться с основами JavaScript, асинхронными операциями вообще и объектами Promises. |
|---|---|
| Цель материала: | Научить писать современный асинхронный код с использованием Promises и async functions. |
Основы async/await
Ключевое слово async
Ключевое слово async позволяет сделать из обычной функции (function declaration или function expression) асинхронную функцию (async function). Такая функция делает две вещи:
— Оборачивает возвращаемое значение в Promise
— Позволяет использовать ключевое слово await (см. дальше)
Попробуйте выполнить в консоли браузера следующий код:
function hello() { return "Hello" };
hello();
Функция возвращает «Hello» — ничего необычного, верно ?
Но что если мы сделаем её асинхронной ? Проверим:
async function hello() { return "Hello" };
hello();
Как было сказано ранее, вызов асинхронной функции возвращает объект Promise.
Вот пример с async function expression:
let hello = async function() { return "Hello" };
hello();
Также можно использовать стрелочные функции:
let hello = async () => { return "Hello" };
Все они в общем случае делают одно и то же.
Чтобы получить значение, которое возвращает Promise, мы как обычно можем использовать метод .then():
hello().then((value) => console.log(value))
или ещё короче
hello().then(console.log)
Итак, ключевое слово async, превращает обычную функцию в асинхронную и результат вызова функции оборачивает в Promise. Также асинхронная функция позволяет использовать в своём теле ключевое слово await, о котором далее.
Ключевое слово await
Асинхронные функции становятся по настоящему мощными, когда вы используете ключевое слово await — по факту, await работает только в асинхронных функциях. Мы можем использовать await перед promise-based функцией, чтобы остановить поток выполнения и дождаться результата её выполнения (результат Promise). В то же время, остальной код нашего приложения не блокируется и продолжает работать.
Вы можете использовать await перед любой функцией, что возвращает Promise, включая Browser API функции.
Небольшой пример:
async function hello() {
return greeting = await Promise.resolve("Hello");
};
hello().then(alert);
Конечно, на практике код выше бесполезен, но в учебных целях он иллюстрирует синтаксис асинхронных функций. Теперь давайте перейдём к реальным примерам.
Переписываем Promises с использованием async/await
Давайте посмотрим на пример из предыдущей статьи:
fetch('coffee.jpg')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
К этому моменту вы должны понимать как работают Promises, чтобы понять все остальное. Давайте перепишем код используя async/await и оценим разницу.
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
Согласитесь, что код стал короче и понятнее — больше никаких блоков .then() по всему скрипту!
Так как ключевое слово async заставляет функцию вернуть Promise, мы можем использовать гибридный подход:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return await response.blob();
}
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}).catch(e => console.log(e));
Можете попрактиковаться самостоятельно, или запустить наш live example (а также source code).
Минуточку, а как это все работает ?
Вы могли заметить, что мы обернули наш код в функцию и сделали её асинхронной с помощью async. Это было обязательно — нам надо создать контейнер, внутри которого будет запускаться асинхронный код и будет возможность дождаться его результата с помощью await, не блокируя остальной код нашего скрипта.
Внутри myFetch() находится код, который слегка напоминает версию на Promise, но есть важные отличия. Вместо того, чтобы писать цепочку блоков .then() мы просто использует ключевое слово await перед вызовом promise-based функции и присваиваем результат в переменную. Ключевое слово await говорит JavaScript runtime приостановить код в этой строке, не блокируя остальной код скрипта за пределами асинхронной функции. Когда вызов promise-based функции будет готов вернуть результат, выполнение продолжится с этой строки дальше.
Пример:
let response = await fetch('coffee.jpg');
Значение Promise, которое вернёт fetch() будет присвоено переменной response только тогда, когда оно будет доступно — парсер делает паузу на данной строке дожидаясь этого момента. Как только значение доступно, парсер переходит к следующей строке, в которой создаётся объект Blob из результата Promise. В этой строке, кстати, также используется await, потому что метод .blob() также возвращает Promise. Когда результат готов, мы возвращаем его наружу из myFetch().
Обратите внимание, когда мы вызываем myFetch(), она возвращает Promise, поэтому мы можем вызвать .then() на результате, чтобы отобразить его на экране.
К этому моменту вы наверное думаете «Это реально круто!», и вы правы — чем меньше блоков .then(), тем легче читать код.
Добавляем обработку ошибок
Чтобы обработать ошибки у нас есть несколько вариантов
Мы можем использовать синхронную try...catch структуру с async/await. Вот изменённая версия первого примера выше:
async function myFetch() {
try {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
} catch(e) {
console.log(e);
}
}
myFetch();
В блок catch() {} передаётся объект ошибки, который мы назвали e; мы можем вывести его в консоль, чтобы посмотреть детали: где и почему возникла ошибка.
Если вы хотите использовать гибридный подходы (пример выше), лучше использовать блок .catch() после блока .then() вот так:
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return await response.blob();
}
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch((e) =>
console.log(e)
);
Так лучше, потому что блок .catch() словит ошибки как из асинхронной функции, так и из Promise. Если бы мы использовали блок try/catch, мы бы не словили ошибку, которая произошла в самой myFetch() функции.
Вы можете посмотреть оба примера на GitHub:
- simple-fetch-async-await-try-catch.html (смотреть source code)
- simple-fetch-async-await-promise-catch.html (смотреть source code)
Await и Promise.all()
Как вы помните, асинхронные функции построены поверх promises, поэтому они совместимы со всеми возможностями последних. Мы легко можем подождать выполнение Promise.all(), присвоить результат в переменную и все это сделать используя синхронный стиль. Опять, вернёмся к примеру, рассмотренному в предыдущей статье. Откройте пример в соседней вкладке, чтобы лучше понять разницу.
Версия с async/await (смотрите live demo и source code), сейчас выглядит так:
async function fetchAndDecode(url, type) {
let response = await fetch(url);
let content;
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if(type === 'blob') {
content = await response.blob();
} else if(type === 'text') {
content = await response.text();
}
return content;
}
}
async function displayContent() {
let coffee = fetchAndDecode('coffee.jpg', 'blob');
let tea = fetchAndDecode('tea.jpg', 'blob');
let description = fetchAndDecode('description.txt', 'text');
let values = await Promise.all([coffee, tea, description]);
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);
let para = document.createElement('p');
para.textContent = descText;
document.body.appendChild(para);
}
displayContent()
.catch((e) =>
console.log(e)
);
Вы видите, что мы легко изменили fetchAndDecode() функцию в асинхронный вариант. Взгляните на строку с Promise.all():
let values = await Promise.all([coffee, tea, description]);
С помощью await мы ждём массив результатов всех трёх Promises и присваиваем его в переменную values. Это асинхронный код, но он написан в синхронном стиле, за счёт чего он гораздо читабельнее.
Мы должны обернуть весь код в синхронную функцию, displayContent(), и мы не сильно сэкономили на количестве кода, но мы извлекли код блока .then(), за счёт чего наш код стал гораздо чище.
Для обработки ошибок мы добавили блок .catch() для функции displayContent(); Это позволило нам отловить ошибки в обоих функциях.
Примечание: Мы также можем использовать синхронный блок finally внутри асинхронной функции, вместо асинхронного .finally(), чтобы получить информацию о результате нашей операции — смотрите в действии в нашем live example (смотрите source code).
Недостатки async/await
Асинхронные функции с async/await бывают очень удобными, но есть несколько замечаний, о которых полезно знать.
Async/await позволяет вам писать код в синхронном стиле. Ключевое слово await блокирует приостанавливает выполнение ptomise-based функции до того момента, пока promise примет статус fulfilled. Это не блокирует код за пределами вашей асинхронной функции, тем не менее важно помнить, что внутри асинхронной функции поток выполнения блокируется.
ваш код может стать медленнее за счёт большого количества awaited promises, которые идут один за другим. Каждый await должен дождаться выполнения предыдущего, тогда как на самом деле мы хотим, чтобы наши Promises выполнялись одновременно, как если бы мы не использовали async/await.
Есть подход, который позволяет обойти эту проблему — сохранить все выполняющиеся Promises в переменные, а уже после этого дожидаться (awaiting) их результата. Давайте посмотрим на несколько примеров.
Мы подготовили два примера — slow-async-await.html (см. source code) и fast-async-await.html (см. source code). Они оба начинаются с функции возвращающей promise, имитирующей асинхронность процессов при помощи вызова setTimeout():
function timeoutPromise(interval) {
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve("done");
}, interval);
});
};
Далее в каждом примере есть асинхронная функция timeTest() ожидающая три вызова timeoutPromise():
async function timeTest() {
...
}
В каждом примере функция записывает время начала исполнения и сколько времени понадобилось на исполнение timeTest() промисов, вычитая время в момент запуска функции из времени в момент разрешения промисов:
let startTime = Date.now();
timeTest().then(() => {
let finishTime = Date.now();
let timeTaken = finishTime - startTime;
alert("Time taken in milliseconds: " + timeTaken);
})
Далее представлена асинхронная функция timeTest() различная для каждого из примеров.
В случае с медленным примером slow-async-await.html, timeTest() выглядит:
async function timeTest() {
await timeoutPromise(3000);
await timeoutPromise(3000);
await timeoutPromise(3000);
}
Здесь мы просто ждём все три timeoutPromise() напрямую, блокируя выполнение на данного блока на 3 секунды при каждом вызове. Все последующие вызовы вынуждены ждать пока разрешится предыдущий. Если вы запустите первый пример (slow-async-await.html) вы увидите alert сообщающий время выполнения около 9 секунд.
Во втором fast-async-await.html примере, функция timeTest() выглядит как:
async function timeTest() {
const timeoutPromise1 = timeoutPromise(3000);
const timeoutPromise2 = timeoutPromise(3000);
const timeoutPromise3 = timeoutPromise(3000);
await timeoutPromise1;
await timeoutPromise2;
await timeoutPromise3;
}
В данном случае мы храним три объекта Promise в переменных, каждый из которых может разрешиться независимо от других.
Ниже мы ожидаем разрешения промисов из объекта в результат, так как они были запущенны одновременно, блокируя поток, то и разрешатся одновременно. Если вы запустите второй пример вы увидите alert, сообщающий время выполнения около 3 секунд.
Важно не забывать о быстродействии применяя await, проверяйте количество блокировок.
Async/await class methods
В качестве последнего замечания, вы можете использовать async перед методами классов или объектов, вынуждая их возвращать promises. А также await внутри методов объявленных таким образом. Посмотрите на пример ES class code, который мы наблюдали в статье object-oriented JavaScript, и сравните его с модифицированной (асинхронной) async версией ниже:
class Person {
constructor(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
}
async greeting() {
return await Promise.resolve(`Hi! I'm ${this.name.first}`);
};
farewell() {
console.log(`${this.name.first} has left the building. Bye for now!`);
};
}
let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
Первый метод класса теперь можно использовать таким образом:
han.greeting().then(console.log);
Browser support (Поддержка браузерами)
One consideration when deciding whether to use async/await is support for older browsers. They are available in modern versions of most browsers, the same as promises; the main support problems come with Internet Explorer and Opera Mini.
If you want to use async/await but are concerned about older browser support, you could consider using the BabelJS library — this allows you to write your applications using the latest JavaScript and let Babel figure out what changes if any are needed for your user’s browsers. On encountering a browser that does not support async/await, Babel’s polyfill can automatically provide fallbacks that work in older browsers.
Заключение
Вот пожалуй и все — async/await позволяют писать асинхронный код, который легче читать и поддерживать. Даже учитывая, что поддержка со стороны браузеров несколько хуже, чем у promise.then, всё же стоит обратить на него внимание.
- Назад
- Обзор: Asynchronous
- Далее (en-US)
In this module
Async/await
Существует специальный синтаксис для работы с промисами, который называется «async/await». Он удивительно прост для понимания и использования.
Асинхронные функции
Начнём с ключевого слова async. Оно ставится перед функцией, вот так:
async function f() { return 1; }
У слова async один простой смысл: эта функция всегда возвращает промис. Значения других типов оборачиваются в завершившийся успешно промис автоматически.
Например, эта функция возвратит выполненный промис с результатом 1:
async function f() { return 1; } f().then(alert); // 1
Можно и явно вернуть промис, результат будет одинаковым:
async function f() { return Promise.resolve(1); } f().then(alert); // 1
Так что ключевое слово async перед функцией гарантирует, что эта функция в любом случае вернёт промис. Согласитесь, достаточно просто? Но это ещё не всё. Есть другое ключевое слово — await, которое можно использовать только внутри async-функций.
Await
Синтаксис:
// работает только внутри async–функций let value = await promise;
Ключевое слово await заставит интерпретатор JavaScript ждать до тех пор, пока промис справа от await не выполнится. После чего оно вернёт его результат, и выполнение кода продолжится.
В этом примере промис успешно выполнится через 1 секунду:
async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("готово!"), 1000) }); *!* let result = await promise; // будет ждать, пока промис не выполнится (*) */!* alert(result); // "готово!" } f();
В данном примере выполнение функции остановится на строке (*) до тех пор, пока промис не выполнится. Это произойдёт через секунду после запуска функции. После чего в переменную result будет записан результат выполнения промиса, и браузер отобразит alert-окно «готово!».
Обратите внимание, хотя await и заставляет JavaScript дожидаться выполнения промиса, это не отнимает ресурсов процессора. Пока промис не выполнится, JS-движок может заниматься другими задачами: выполнять прочие скрипты, обрабатывать события и т.п.
По сути, это просто «синтаксический сахар» для получения результата промиса, более наглядный, чем promise.then.
««warn header=»await нельзя использовать в обычных функциях»
Если мы попробуем использовать `await` внутри функции, объявленной без `async`, получим синтаксическую ошибку:
function f() { let promise = Promise.resolve(1); *!* let result = await promise; // SyntaxError */!* }
Ошибки не будет, если мы укажем ключевое слово async перед объявлением функции. Как было сказано раньше, await можно использовать только внутри async–функций.
Давайте перепишем пример `showAvatar()` из раздела <info:promise-chaining> с помощью `async/await`:
1. Нам нужно заменить вызовы `.then` на `await`.
2. И добавить ключевое слово `async` перед объявлением функции.
```js run
async function showAvatar() {
// запрашиваем JSON с данными пользователя
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// запрашиваем информацию об этом пользователе из github
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// отображаем аватар пользователя
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// ждём 3 секунды и затем скрываем аватар
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
```
Получилось очень просто и читаемо, правда? Гораздо лучше, чем раньше.
````smart header="`await` нельзя использовать на верхнем уровне вложенности"
Программисты, узнав об `await`, часто пытаются использовать эту возможность на верхнем уровне вложенности (вне тела функции). Но из-за того, что `await` работает только внутри `async`–функций, так сделать не получится:
```js run
// SyntaxError на верхнем уровне вложенности
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
```
Можно обернуть этот код в анонимную `async`–функцию, тогда всё заработает:
```js
(async () => {
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
...
})();
```
««smart header=»await работает с «thenable»–объектами»
Как и `promise.then`, `await` позволяет работать с промис–совместимыми объектами. Идея в том, что если у объекта можно вызвать метод `then`, этого достаточно, чтобы использовать его с `await`.
В примере ниже, экземпляры класса Thenable будут работать вместе с await:
class Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // выполнить resolve со значением this.num * 2 через 1000мс setTimeout(() => resolve(this.num * 2), 1000); // (*) } }; async function f() { // код будет ждать 1 секунду, // после чего значение result станет равным 2 let result = await new Thenable(1); alert(result); } f();
Когда await получает объект с .then, не являющийся промисом, JavaScript автоматически запускает этот метод, передавая ему аргументы – встроенные функции resolve и reject. Затем await приостановит дальнейшее выполнение кода, пока любая из этих функций не будет вызвана (в примере это строка (*)). После чего выполнение кода продолжится с результатом resolve или reject соответственно.
````smart header="Асинхронные методы классов"
Для объявления асинхронного метода достаточно написать `async` перед именем:
```js run
class Waiter {
*!*
async wait() {
*/!*
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1
```
Как и в случае с асинхронными функциями, такой метод гарантированно возвращает промис, и в его теле можно использовать `await`.
Обработка ошибок
Когда промис завершается успешно, await promise возвращает результат. Когда завершается с ошибкой – будет выброшено исключение. Как если бы на этом месте находилось выражение throw.
Такой код:
async function f() { *!* await Promise.reject(new Error("Упс!")); */!* }
Делает то же самое, что и такой:
async function f() { *!* throw new Error("Упс!"); */!* }
Но есть отличие: на практике промис может завершиться с ошибкой не сразу, а через некоторое время. В этом случае будет задержка, а затем await выбросит исключение.
Такие ошибки можно ловить, используя try..catch, как с обычным throw:
async function f() { try { let response = await fetch('http://no-such-url'); } catch(err) { *!* alert(err); // TypeError: failed to fetch */!* } } f();
В случае ошибки выполнение try прерывается и управление прыгает в начало блока catch. Блоком try можно обернуть несколько строк:
async function f() { try { let response = await fetch('/no-user-here'); let user = await response.json(); } catch(err) { // перехватит любую ошибку в блоке try: и в fetch, и в response.json alert(err); } } f();
Если у нас нет try..catch, асинхронная функция будет возвращать завершившийся с ошибкой промис (в состоянии rejected). В этом случае мы можем использовать метод .catch промиса, чтобы обработать ошибку:
async function f() { let response = await fetch('http://no-such-url'); } // f() вернёт промис в состоянии rejected *!* f().catch(alert); // TypeError: failed to fetch // (*) */!*
Если забыть добавить .catch, то будет сгенерирована ошибка «Uncaught promise error» и информация об этом будет выведена в консоль. Такие ошибки можно поймать глобальным обработчиком, о чём подробно написано в разделе info:promise-error-handling.
«`smart header=»async/await и `promise.then/catch`»
При работе с `async/await`, `.then` используется нечасто, так как `await` автоматически ожидает завершения выполнения промиса. В этом случае обычно (но не всегда) гораздо удобнее перехватывать ошибки, используя `try..catch`, нежели чем `.catch`.
Но на верхнем уровне вложенности (вне async–функций) await использовать нельзя, поэтому .then/catch для обработки финального результата или ошибок – обычная практика.
Так сделано в строке (*) в примере выше.
````smart header="`async/await` отлично работает с `Promise.all`"
Когда необходимо подождать несколько промисов одновременно, можно обернуть их в `Promise.all`, и затем `await`:
```js
// await будет ждать массив с результатами выполнения всех промисов
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
В случае ошибки она будет передаваться как обычно: от завершившегося с ошибкой промиса к Promise.all. А после будет сгенерировано исключение, которое можно отловить, обернув выражение в try..catch.
## Итого
Ключевое слово `async` перед объявлением функции:
1. Обязывает её всегда возвращать промис.
2. Позволяет использовать `await` в теле этой функции.
Ключевое слово `await` перед промисом заставит JavaScript дождаться его выполнения, после чего:
1. Если промис завершается с ошибкой, будет сгенерировано исключение, как если бы на этом месте находилось `throw`.
2. Иначе вернётся результат промиса.
Вместе они предоставляют отличный каркас для написания асинхронного кода. Такой код легко и писать, и читать.
Хотя при работе с `async/await` можно обходиться без `promise.then/catch`, иногда всё-таки приходится использовать эти методы (на верхнем уровне вложенности, например). Также `await` отлично работает в сочетании с `Promise.all`, если необходимо выполнить несколько задач параллельно.