Fetch await error

Используем fetch вместе с async/await для полного цикла запроса данных, включая обработку ошибок и прерывание запросов.

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().

В статье мы разобрали, как использовать эту комбинацию для получения данных, обработки ошибок, отмены запросов и выполнения параллельных запросов.

Cover image for When That's Not So Fetch: Error Handling With fetch()

Anthony Chung

The fetch() method in JavaScript is a global, asynchronous method that allows us to interface with API’s for requests and responses. While this is a powerful and commonly-used tool, its error handling process may seem a bit elusive at start.

Why Error Handling?

When errors are encountered during a fetch() call, it is often necessary to halt the compiler from reading the next few lines of code. To do so, the method call should throw an error upon encountering one. The thrown error can be «caught» later on for an alternate behavior to take place. Although one might think that the fetch() call would automatically throw an error upon encountering one, that is not the case for JavaScript.

According to the fetch() MDN, the Promise object returned by the fetch() call is rejected (throws an error) only when «a network error is encountered.» This means that fetch() Promises do resolve despite encountering client-side HTTP errors such as 404 and do not throw errors during the fetch. Therefore, the code shown below would log «Success» instead of «Error» when run, which may seem unexpected.

fetch(url) // encounters a 404 error
   .then(res => res.json()) // no error is thrown
   .then(() => console.log("Success")) // 
   .catch(() => console.log("Error")) // fails to catch error

Enter fullscreen mode

Exit fullscreen mode

Luckily, you can fix this quite simply by using proper error handling.

Handling fetch() Errors

fetch() calls can be made using either Promise chains or Async/Await. Fortunately, the error handling process is similar for both.

Using Promises

The fetch API provides an ok property to the Promise response which indicates whether the HTTP status is within the range 200-299 (inclusive). This can be used to check whether any error is encountered during fetch.

const handleError = response => {
   if (!response.ok) { 
      throw Error(response.statusText);
   } else {
      return response.json();
   }
}; //handler function that throws any encountered error

fetch(url)
   .then(handleError) // skips to .catch if error is thrown
   .then(data => console.log("Does something with data"))
   .catch(console.log); // catches the error and logs it

Enter fullscreen mode

Exit fullscreen mode

The error-handler function should be called before the Promise response is parsed by .json(). Otherwise, the .json() method would strip out the response properties necessary for error handling (such as ok, status, and statusText).

Using Async/Await

Error handling using Async/Await uses a slightly different syntax, but it also revolves around the idea of using the ok property to check whether any error is encountered or not.

const response = await fetch(url);
if (!response.ok) {
   console.log(response.status, response.statusText);
} else {
   const data = await response.json();
   console.log(data);
}

Enter fullscreen mode

Exit fullscreen mode

The status response property provides the status code (e.g. «404») while the statusText response property provides the status description (e.g. «Is Not Found»).

Conclusion

Although the error handling for fetch() may not seem intuitive at first, it will later make more sense since it provides the user with more control over unique situations.

Overall, error-handling for fetch() calls is a simple and user-friendly tool that will definitely aid you in the long-term.

Resources

  • Handling Failed HTTP Responses With fetch()
  • Error handling while using native fetch API in JavaScript
  • Using Fetch MDN

May 23, 2022

Umar Hansa

On this page

  • Anticipate potential network errors
    • Examples of user errors
    • Examples of environmental changes
    • Examples of errors with the video-sharing website
  • Handle errors with the Fetch API
    • When the Fetch API throws errors
    • When the network status code represents an error
    • When there is an error parsing the network response
    • When the network request must be canceled before it completes
  • Conclusion

This article demonstrates some error handling approaches when working with the Fetch API. The Fetch API lets you make a request to a remote network resource. When you make a remote network call, your web page becomes subject to a variety of potential network errors.

The following sections describe potential errors and describe how to write code that provides a sensible level of functionality that is resilient to errors and unexpected network conditions. Resilient code keeps your users happy and maintains a standard level of service for your website.

Anticipate potential network errors #

This section describes a scenario in which the user creates a new video named "My Travels.mp4" and then attempts to upload the video to a video-sharing website.

When working with Fetch, it’s easy to consider the happy path where the user successfully uploads the video. However, there are other paths that are not as smooth, but for which web developers must plan. Such (unhappy) paths can happen due to user error, through unexpected environmental conditions, or because of a bug on the video-sharing website.

Examples of user errors #

  • The user uploads an image file (such as JPEG) instead of a video file.
  • The user begins uploading the wrong video file. Then, part way through the upload, the user specifies the correct video file for upload.
  • The user accidentally clicks «Cancel upload» while the video is uploading.

Examples of environmental changes #

  • The internet connection goes offline while the video is uploading.
  • The browser restarts while the video is uploading.
  • The servers for the video-sharing website restart while the video is uploading.

Examples of errors with the video-sharing website #

  • The video-sharing website cannot handle a filename with a space. Instead of "My Travels.mp4", it expects a name such as "My_Travels.mp4" or "MyTravels.mp4".
  • The video-sharing website cannot upload a video that exceeds the maximum acceptable file size.
  • The video-sharing website does not support the video codec in the uploaded video.

These examples can and do happen in the real world. You may have encountered such examples in the past! Let’s pick one example from each of the previous categories, and discuss the following points:

  • What is the default behavior if the video-sharing service cannot handle the given example?
  • What does the user expect to happen in the example?
  • How can we improve the process?
Action The user begins uploading the wrong video file. Then, part way through the upload, the user specifies the correct video file for upload.
What happens by default The original file continues to upload in the background while the new file uploads at the same time.
What the user expects The user expects the original upload to stop so that no extra internet bandwidth is wasted.
What can be improved JavaScript cancels the Fetch request for the original file before the new file begins to upload.
Action The user loses their internet connection part way through uploading the video.
What happens by default The upload progress bar appears to be stuck on 50%. Eventually, the Fetch API experiences a timeout and the uploaded data is discarded. When internet connectivity returns, the user has to reupload their file.
What the user expects The user expects to be notified when their file cannot be uploaded, and they expect their upload to automatically resume at 50% when they are back online.
What can be improved The upload page informs the user of internet connectivity issues, and reassures the user that the upload will resume when internet connectivity has resumed.
Action The video-sharing website cannot handle a filename with a space. Instead of «My Travels.mp4», it expects names such as «My_Travels.mp4» or «MyTravels.mp4».
What happens by default The user must wait for the upload to completely finish. Once the file is uploaded, and the progress bar reads «100%», the progress bar displays the message: «Please try again.»
What the user expects The user expects to be told of filename limitations before upload begins, or at least within the first second of uploading.
What can be improved Ideally, the video-sharing service supports filenames with spaces. Alternative options are to notify the user of filename limitations before uploading begins. Or, the video-sharing service should reject the upload with a detailed error message.

Handle errors with the Fetch API #

Note that the following code examples use top-level await (browser support) because this feature can simplify your code.

When the Fetch API throws errors #

This example uses a try/catch block statement to catch any errors thrown within the try block. For example, if the Fetch API cannot fetch the specified resource, then an error is thrown. Within a catch block like this, take care to provide a meaningful user experience. If a spinner, a common user interface that represents some sort of progress, is shown to the user, then you could take the following actions within a catch block:

  1. Remove the spinner from the page.
  2. Provide helpful messaging that explains what went wrong, and what options the user can take.
  3. Based on the available options, present a «Try again» button to the user.
  4. Behind the scenes, send the details of the error to your error-tracking service, or to the back-end. This action logs the error so it can be diagnosed at a later stage.
try {
const response = await fetch('https://website');
} catch (error) {
// TypeError: Failed to fetch
console.log('There was an error', error);
}

At a later stage, while you diagnose the error that you logged, you can write a test case to catch such an error before your users are aware something is wrong. Depending on the error, the test could be a unit, integration, or acceptance test.

When the network status code represents an error #

This code example makes a request to an HTTP testing service that always responds with the HTTP status code 429 Too Many Requests. Interestingly, the response does not reach the catch block. A 404 status, amongst certain other status codes, does return a network error but instead resolves normally.

To check that the HTTP status code was successful, you can use any of the following options:

  • Use the Response.ok property to determine whether the status code was in the range from 200 to 299.
  • Use the Response.status property to determine whether the response was successful.
  • Use any other metadata, such as Response.headers, to assess whether the response was successful.
let response;

try {
response = await fetch('https://httpbin.org/status/429');
} catch (error) {
console.log('There was an error', error);
}

// Uses the 'optional chaining' operator
if (response?.ok) {
console.log('Use the response here!');
} else {
console.log(`HTTP Response Code: ${response?.status}`)
}

The best practice is to work with people in your organization and team to understand potential HTTP response status codes. Backend developers, developer operations, and service engineers can sometimes provide unique insight into possible edge cases that you might not anticipate.

When there is an error parsing the network response #

This code example demonstrates another type of error that can arise with parsing a response body. The Response interface offers convenient methods to parse different types of data, such as text or JSON. In the following code, a network request is made to an HTTP testing service that returns an HTML string as the response body. However, an attempt is made to parse the response body as JSON, throwing an error.

let json;

try {
const response = await fetch('https://httpbin.org/html');
json = await response.json();
} catch (error) {
if (error instanceof SyntaxError) {
// Unexpected token < in JSON
console.log('There was a SyntaxError', error);
} else {
console.log('There was an error', error);
}
}

if (json) {
console.log('Use the JSON here!', json);
}

You must prepare your code to take in a variety of response formats, and verify that an unexpected response doesn’t break the web page for the user.

Consider the following scenario: You have a remote resource that returns a valid JSON response, and it is parsed successfully with the Response.json() method. It may happen that the service goes down. Once down, a 500 Internal Server Error is returned. If appropriate error-handling techniques are not used during the parsing of JSON, this could break the page for the user because an unhandled error is thrown.

When the network request must be canceled before it completes #

This code example uses an AbortController to cancel an in-flight request. An in-flight request is a network request that has started but has not completed.

The scenarios where you may need to cancel an in-flight request can vary, but it ultimately depends on your use case and environment. The following code demonstrates how to pass an AbortSignal to the Fetch API. The AbortSignal is attached to an AbortController, and the AbortController includes an abort() method, which signifies to the browser that the network request should be canceled.

const controller = new AbortController();
const signal = controller.signal;

// Cancel the fetch request in 500ms
setTimeout(() => controller.abort(), 500);

try {
const url = 'https://httpbin.org/delay/1';
const response = await fetch(url, { signal });
console.log(response);
} catch (error) {
// DOMException: The user aborted a request.
console.log('Error: ', error)
}

Conclusion #

One important aspect of handling errors is to define the various parts that can go wrong. For each scenario, make sure you have an appropriate fallback in place for the user. With regards to a fetch request, ask yourself questions such as:

  • What happens if the target server goes down?
  • What happens if Fetch receives an unexpected response?
  • What happens if the user’s internet connection fails?

Depending on the complexity of your web page, you can also sketch out a flowchart which describes the functionality and user interface for different scenarios.

Return to all articles

  • Назад
  • Обзор: 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

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Ffr 00771 06 ошибка ман тга
  • Feststellbremse einlegen ошибка ман
  • Festinationis comites sunt error et poenitentia произношение
  • Festinationis comites sunt error et poenitentia перевод
  • Ferroli ошибка ф37

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии