JavaScript: Введение в Fetch API

Источник: «Introduction to the Fetch API»
Как выглядит Fetch API, какие проблемы решает, и какой способ получения удалённых данных с помощью функции fetch() внутри веб-страницы наиболее практичен.

В течение многих лет XMLHttpRequest был надёжным помощником веб-разработчиков. Напрямую или скрыто, XMLHttpRequest поддерживает Ajax и новый (на момент написания статьи) тип интерактивного взаимодействия, от Gmail до Facebook.

Однако XMLHttpRequest постепенно заменяется Fetch API. Оба могут использоваться для выполнения сетевых запросов, но Fetch API основан на промисах, что обеспечивает более чистый и лаконичный синтаксис, и помогает избежать ада обратных вызовов

Fetch API

Fetch API предоставляет метод fetch(), определённый для объекта window используемый для выполнения запросов. Этот метод возвращает Promise, который можно использовать для получения ответа на запрос.

Метод fetch() имеет только один обязательный аргумент — URL-адрес ресурса, который вы хотите получать. Очень простой пример будет выглядеть так. Получает пять лучших сообщений из r/javascript на Reddit:

fetch('https://www.reddit.com/r/javascript/top/.json?limit=5')
.then(res => console.log(res));

Если вы посмотрите ответ в консоли браузера, то увидите объект Response с несколькими свойствами:

{
body: ReadableStream
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: ""
type: "cors"
url: "https://www.reddit.com/top/.json?count=5"
}

Вроде бы запрос выполнился, но где пять лучших сообщений? Давайте выясним.

Загрузка JSON

Мы не можем заблокировать пользовательский интерфейс в ожидании завершения запроса. Вот почему fetch() возвращает Promise — объект, представляющий будущий результат. В приведённом выше примере мы использовали метод then, чтобы дождаться ответа сервера и вывести его в консоль.

Теперь давайте посмотрим, как мы можем извлечь полезную нагрузку JSON из этого ответа после завершения запроса:

fetch('https://www.reddit.com/r/javascript/top/.json?limit=5')
.then(res => res.json())
.then(json => console.log(json));

Мы запускаем запрос, вызывая fetch(). Когда промис выполнен, он возвращает объект Response предоставляющий метод json. В первом then() мы можем вызвать метод json, чтобы получить тело ответа в JSON.

Однако метод json также возвращает промис, а это значит, что нам нужно выполнить цепочку с другим then(), прежде, чем ответ JSON будет выведен в консоль.

И почему json() возвращает промис? Поскольку HTTP позволяет передавать контент клиенту по частям, поэтому, даже если браузер получает ответ от сервера, тело содержимого может быть ещё не всё!

Async/await

Синтаксис .then() хорош, но более лаконичный способ обработки промисов в 2018 году — это использование async/await — нового синтаксиса, представленного в ES2017. Использование async/await означает, что мы можем пометить функцию как async, затем дождаться завершения промиса с помощью ключевого слова await и получить доступ к результату, как к обычному объекту. Асинхронные функции поддерживаются во всех современных браузерах.

Так будет выглядеть приведённый выше пример (слегка расширенный) с использованием async/await:

async function fetchTopFive(sub) {
const URL = `https://www.reddit.com/r/${sub}/top/.json?limit=5`;
const fetchResult = fetch(URL)
const response = await fetchResult;
const jsonData = await response.json();
console.log(jsonData);
}

fetchTopFive('javascript');

Не так много изменений. Помимо того, что мы создали async функцию, которой передаётся имя сабреддита, теперь мы ожидаем результат вызова fetch(), а затем снова используем await для получения JSON из ответа.

Это базовый рабочий процесс, но с удалёнными службами не всегда всё идёт гладко.

Обработка ошибок

Представьте, что мы запрашиваем у сервера несуществующий ресурс или ресурс, требующий авторизации. С помощью fetch() вы должны обрабатывать ошибки уровня приложения, такие как ответы 404, внутри обычного потока. Как вы видели ранее, fetch() возвращает объект Response со свойством ok. Если response.ok имеет значение true, код состояния ответа находится в диапазоне 200:

async function fetchTopFive(sub) {
const URL = `http://httpstat.us/404`; //Вернёт 404
const fetchResult = fetch(URL)
const response = await fetchResult;
if (response.ok) {
const jsonData = await response.json();
console.log(jsonData);
} else {
throw Error(response.statusText);
}
}

fetchTopFive('javascript');

Значение ответа от сервера варьируется от API к API, и часто проверки response.ok может быть недостаточно. Например, некоторые API возвращают ответ 200, даже если ваш ключ API не действителен. Всегда читайте документацию по API!

Для обработки ошибки соединения, используйте try ... catch

async function fetchTopFive(sub) {
const URL = `https://www.reddit.com/r/${sub}/top/.json?limit=5`;
try {
const fetchResult = fetch(URL)
const response = await fetchResult;
const jsonData = await response.json();
console.log(jsonData);
} catch(e){
throw Error(e);
}
}

fetchTopFive('javvascript'); // Обратите внимание на неправильное написание

Код внутри catch будет выполняться только при возникновении сетевой ошибки.

Вы изучили основы запросов и чтения ответов. Теперь давайте дальше настраивать запрос.

Изменение Метода запроса и Заголовка

Глядя на приведённый выше пример, вы можете задаться вопросом, почему нельзя просто использовать одну из существующих обёрток XMLHttpRequest. Причина в том, что Fetch API предлагает больше, чем просто метод fetch().

Хотя для выполнения запроса и получения ответа необходимо использовать один и тот же экземпляр XMLHttpRequest, Fetch API позволяет явно настраивать объекты запроса.

Например, если вам нужно изменить то, как fetch() делает запрос (например, настроить метод запроса), вы можете передать объект Request в функцию fetch(). Первый аргумент конструктора Request — это URL-адрес запроса, а второй аргумент — объект параметра, который настраивает запрос:

async function fetchTopFive(sub) {
const URL = `https://www.reddit.com/r/${sub}/top/.json?limit=5`;
try {
const fetchResult = fetch(
new Request(URL, { method: 'GET', cache: 'reload' })
);
const response = await fetchResult;
const jsonData = await response.json();
console.log(jsonData);
} catch(e){
throw Error(e);
}
}

fetchTopFive('javascript');

Мы указали метод запроса и попросили его никогда не кэшировать ответ.

Вы можете изменить заголовки запроса, назначив объект Headers в поле заголовка запроса. Вот так запросить содержимое JSON только с заголовком Accept:

const headers = new Headers();
headers.append('Accept', 'application/json');
const request = new Request(URL, { method: 'GET', cache: 'reload', headers: headers });

Вы можете создать новый запрос из старого, чтобы настроить его для другого варианта использования. Например, вы можете создать POST запрос и GET запроса к тому же ресурсу. Вот пример:

const postReq = new Request(request, { method: 'POST' });

Вы также можете получить доступ к заголовкам ответов, но имейте в виду, что они доступны только для чтения.

fetch(request).then(response => console.log(response.headers));

Request и Response точно соответствуют HTTP спецификации; вы должны их знать, если сталкивались с использованием программирования на стороне сервера. Если вам интересно узнать больше, вы можете почитать о них на странице Fetch API на MDN.

Собираем всё вместе

В завершение статьи — рабочий пример, демонстрирующий, как получить пять лучших сообщений из определённого сабреддита и отобразить его в списке.

See the Pen

Попробуйте ввести несколько сабреддитов (например, javascript, node, linux, lolcats), а также пару несуществующих.

Что делать дальше

В этой статье вы узнали, как выглядит новый (для 2018 года) Fetch API и какие проблемы он решает. Я продемонстрировал, как получать удалённые данные с помощью метода fetch(), как обрабатывать ошибки и создавать объекты Request для управления методом и заголовками запроса.

Поэтому в следующий раз, когда вы будете подключать стороннюю библиотеку (например, jQuery) для выполнения ajax запросов. Подумайте не могли бы вы вместо этого использовать нативные методы браузера.

Дополнительные материалы

Дополнительные материалы

Предыдущая Статья

JavaScript: Функциона­льное Выражение vs. Объявление Функции

Следующая Статья

JavaScript: Управление потоком