Js обработка ошибок fetch

Если вы пришли сюда только ради ответа и вам не интересны рассуждения - листайте вниз :)Как все начиналосьДля начала, давайте вспомним, а как вообще ловят ошибки...

Если вы пришли сюда только ради ответа и вам не интересны рассуждения — листайте вниз :)

Как все начиналось

Для начала, давайте вспомним, а как вообще ловят ошибки в js, будь то браузер или сервер. В js есть конструкция try...catch.

try {
    let data = JSON.parse('...');
} catch(err: any) {
		// если произойдет ошибка, то мы окажемся здесь
}

Это общепринятая конструкция и в большинстве языков она есть. Однако, тут есть проблема (и как окажется дальше — не единственная), эта конструкция «не будет работать» для асинхронного кода, для кода который был лет 5 назад. В те времена, в браузере использовали для Ajax запроса XMLHttpRequest.

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com', true);
xhr.addEventListener('error', (e: ProgressEvent<XMLHttpRequestEventTarget>) => {
    // если произойдет ошибка, то мы окажемся здесь
});

Тут используется механизм подписки на событие возникновения ошибки. В данном случае, переменная e является событием, фактически мы ушли от настоящей ошибки и закрылись некоторой абстракцией, за которой спрятана настоящая ошибка, доступа к которой у нас нет.

В NodeJS с самого начала продвигалась концепция Error-First Callback, эта идея применялась для асинхронных функций, например, для чтения файла. Смысл ее в том, чтобы первым аргументом передавать в функцию обратного вызова ошибку, а следующими аргументами уже получаемые данные.

import fs from 'fs';

fs.readFile('file.txt', (err, data) => {
    if (err) {
        // обработка ошибки
    }
    // если все хорошо, работаем с данными
});

Если мы посмотрим какой тип имеет переменная err, то увидим следующее:

interface ErrnoException extends Error {
    errno?: number | undefined;
    code?: string | undefined;
    path?: string | undefined;
    syscall?: string | undefined;
}

Тут действительно находится ошибка. По сути, это тот же способ, что и выше, только в этом случает мы получаем объект Error.

Через некоторое время, в Javascript появились Promise. Они, безусловно, изменили разработку на js к лучшему. Ведь никто* никто не любит городить огромные конструкции из функций обратного вызова.

fetch('https://api.example.com')
  .then(res => {
    // если все хорошо, работаем с данными
  })
  .catch(err => {
		// обработка ошибки
  });

Несмотря на то, что внешне этот пример сильно отличается от первого, тем не менее, мы видим явную логическую связь. Очевидно, что разработчики хотели сделать похожую на try...catch конструкцию. Со временем, появился еще один способ обработать ошибку в асинхронном коде. Этот способ, по сути, является лишь синтаксическим сахаром для предыдущего примера.

try {
  const res = await fetch('https://api.example.com');
  // если все хорошо, работаем с данными
} catch(err) {
	// обработка ошибки
}

Также, конструкция try...catch позволяет ловить ошибки из нескольких промисов одновременно.

try {
  let usersRes = await fetch('https://api.example.com/users');
	let users = await usersRes.json();

  let chatsRes = await fetch('https://api.example.com/chats');
	let chats = await chatsRes.json();

  // если все хорошо, работаем с данными
} catch(err) {
	// обработка ошибки
}

Вот, замечательный вариант ловли ошибок. Любая ошибка которая возникнет внутри блока try, попадет в блок catch и мы точно её обработаем.

А точно ли обработаем?

Действительно, а правда ли, что мы обработаем ошибку, или всего лишь сделаем вид? На практике, скорее всего, возникнувшая ошибка будет просто выведена в консоль или т.п. Более того, при появлении ошибки*, интерпретатор прыгнет в блок catch , где не мы, не TypeScript не сможет вывести тип переменной, попавшей туда (пример — возврат с помощью Promise.reject), после чего, произойдет выход из функции. То есть, мы не сможем выполнить код который находится в этом же блоке, но который расположен ниже функции, внутри которой произошла ошибка. Конечно, мы можем предусмотреть такие ситуации, но сложность кода и читаемость вырастут многократно.

Как быть?

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

let [users, err] = await httpGET('https://api.example.com/users');
if (err !== null) {
	// обработка ошибки
}
// продолжаем выполнение кода

Возможную ошибку мы держим всегда рядом с данными, возвращаемыми из функции, что намекает нам на то, что переменную err желательно проверить.

Пример для вызова нескольких функций возвращающих Promise.

let err: Error,
		users: User[],
		chats: Chat[];

[users, err] = await httpGET('https://api.example.com/users');
if (err !== nil) {
  // обработка ошибки
}

[chats, err] = await httpGET('https://api.example.com/chats');
if (err !== nil) {
  // обработка ошибки
}

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

Давайте рассмотрим как можно реализовать такую функцию и что нам вообще нужно делать. Для начала, давайте определим тип PairPromise. В данном случае, я решил использовать null если результата или ошибки нету, так как он просто короче.

type PairPromise<T> = Promise<[T, null] | [null, Error]>;

Определим возможные возвращаемые ошибки.

const notFoundError = new Error('NOT_FOUND');
const serviceUnavailable = new Error('SERVICE_UNAVAILABLE');

Теперь опишем нашу функцию.

const getUsers = async (): PairPromise<User[]> => {
    try {
        let res = await fetch('https://api.example.com/users');
        if (res.status === 504) {
            return Promise.resolve([null, serviceUnavailable]);
        }

        let users = await res.json() as User[];

        if (users.length === 0) {
            return Promise.resolve([null, notFoundError]);
        }

        return Promise.resolve([users, null]);
    } catch(err) {
        return Promise.resolve([null, err]);
    }
} 

Пример использования такой функции.

let [users, err] = await getUsers();
if (err !== null) {
	switch (err) {
  	case serviceUnavailable:
    	// сервис недоступен
    case notFoundError:
    	// пользователи не найдены
    default:
    	// действие при неизвестной ошибке
	}
}

Вариантов применения данного подхода обработки ошибок очень много. Мы сочетаем удобства конструкции try...catch и Error-First Callback, мы гарантированно поймаем все ошибки и сможем удобно их обработать, при необходимости. Как приятный бонус — мы не теряем типизацию. Также, мы не скованы лишь объектом Error, мы можем возвращать свои обертки и успешно их использовать, в зависимости от наших убеждений.

Очень интересно мнение сообщества на эту тему.

Fetch API используется для выполнения ajax-запросов, для вызова API или выборки удаленного ресурса или получения HTML-файла с сервера.

fetch api

Базовый синтаксис Fetch API

Допустим, нужно получить список сообщений из API https://jsonplaceholder.typicode.com/posts. Во-первых, следует передать этот URL в метод fetch() в качестве аргумента.

fetch('https://jsonplaceholder.typicode.com/posts');

Метод fetch() возвращает Promise. Поэтому ответы API нужно обрабатывать с использованием then() и catch().

fetch('https://jsonplaceholder.typicode.com/posts')
  .then(function (response) {
  // API вернул результат
  console.log('success!', response);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Посмотрите пример.

Посмотрим на ответ в консоли: сразу можно обратить внимание, в response.body нет JSON. Это — ReadableStream.

Fetch API использует потоки. Чтобы получить данные из API в виде объекта JSON, можно использовать оригинальный метод Fetch API: json().

fetch('https://jsonplaceholder.typicode.com/posts')
  .then(function (response) {
  // API вернул результат
  return response.json();
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Посмотрите, теперь тут нормальный JSON.

Обработка ошибок для Fetch API

Fetch API обрабатывает ошибки с помощью метода catch(), потому что он возвращает Promise.

Однако, Promise отклоняется и вызывает метод catch() только в случае сбоя. Если вернулся ответ от сервера, даже в случае с ошибками 404 или 500, будут работать методы then().

Чтобы пофиксить такое поведение, можно использовать свойство ok в response, который возвращает Promise.

Если у свойства response.ok значение true, вернём response.json(). Если нет, то вернём отклоненный объект Promise и передадим в нём ответ, чтобы вызвать метод catch().

fetch('https://jsonplaceholder.typicode.com/postsQQQQ')
  .then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Так лучше обрабатывать ошибки в методе Fetch.

В этом примере умышленно искажён URL, он вернёт 404 ошибку, чтобы продемонстрировать отклонение Promise вручную для принудительного вызова метода catch().

То же самое можно делать с помощью XHR

Обработчик onreadystatechange в XHR тоже выполняется независимо от того, был ответ успешным или нет. Здесь тоже нужно проверять, находится ли ответ между 200 и 300, прежде чем работать с ним.

xhr.onreadystatechange = function () {
  // Вернём, когда работа метода завершится
  if (xhr.readyState !== 4) return;
    // обработаем ответ
    if (xhr.status >= 200 && xhr.status < 300) {
      // Ответ нормальный
      console.log('success', JSON.parse(xhr.responseText));
    } else {
      // Ответ плохой
      console.log('error', xhr);
  }
};

У Fetch API код получился намного чище, сравните.

Создание других типов запросов

По умолчанию Fetch API выполняет GET-запросы. Допустим, нужно сделать POST-запрос для публикации новой статьи через API. Методу fetch() надо в этом случае передавать второй аргумент, который можно использовать для передачи объекта с параметрами.

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST'
}).then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Для определенных типов запросов может понадобиться передавать данные вместе с запросом. Это можно сделать с помощью свойства body в объекте параметров.

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  body: 'title=' + encodeURIComponent('Заголовок') +
        '&body=' + encodeURIComponent('Текст статьи')
}).then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Установка заголовков с помощью Fetch API

Еще одна распространенная вещь, которую может потребоваться сделать, установить для запроса заголовки и другие свойства. Это также можно сделать с помощью объекта параметров.

fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  body: 'title=' + encodeURIComponent('Заголовок') +
        '&body=' + encodeURIComponent('Текст статьи'),
  headers: {
    'Content-Type': 'application/json'
  },
  referrer: 'no-referrer'
}).then(function (response) {
  // API вернул результат
  if (response.ok) {
    return response.json();
  } else {
    console.log('Это не 200OK')
    return Promise.reject(response);
  }
}).then(function (data) {
  // Это уже JSON
  console.log(data);
}).catch(function (err) {
  // Что-то пошло не так
  console.warn('Ошибка получения.');
  console.log(err);
});

Демка

Здесь можно поэкспериментировать с кросс-доменными запросами (CORS).

See this code cors fetch on x.xhtml.ru.

How to use the Fetch API with vanilla JS

Fetch API предоставляет интерфейс JavaScript для работы с запросами и ответами HTTP. Он также предоставляет глобальный метод fetch() (en-US), который позволяет легко и логично получать ресурсы по сети асинхронно.

Подобная функциональность ранее достигалась с помощью XMLHttpRequest. Fetch представляет собой лучшую альтернативу, которая может быть легко использована другими технологиями, такими как Service Workers (en-US). Fetch также обеспечивает единое логическое место для определения других связанных с HTTP понятий, такие как CORS и расширения для HTTP.

Обратите внимание, fetch спецификация отличается от jQuery.ajax() в основном в двух пунктах:

  • Promise возвращаемый вызовом fetch() не перейдёт в состояние «отклонено» из-за ответа HTTP, который считается ошибкой, даже если ответ HTTP 404 или 500. Вместо этого, он будет выполнен нормально (с значением false в статусе ok ) и будет отклонён только при сбое сети или если что-то помешало запросу выполниться.
  • По умолчанию, fetch не будет отправлять или получать cookie файлы с сервера, в результате чего запросы будут осуществляться без проверки подлинности, что приведёт к неаутентифицированным запросам, если сайт полагается на проверку пользовательской сессии (для отправки cookie файлов в аргументе init options должно быть задано значение свойства credentials отличное от значения по умолчанию omit).

Примечание: 25 августа 2017 г. в спецификации изменилось значение по умолчанию свойства credentials на same-origin. Firefox применяет это изменение с версии 61.0b13.

Базовый запрос на получение данных действительно прост в настройке. Взгляните на следующий код:

fetch('http://example.com/movies.json')
  .then((response) => {
    return response.json();
  })
  .then((data) => {
    console.log(data);
  });

Здесь мы забираем JSON файл по сети и выводим его содержимое в консоль. Самый простой способ использования fetch() заключается в вызове этой функции с одним аргументом — строкой, содержащей путь к ресурсу, который вы хотите получить — которая возвращает promise, содержащее ответ (объект Response).

Конечно, это просто HTTP-ответ, а не фактический JSON. Чтобы извлечь содержимое тела JSON из ответа, мы используем json() (en-US) метод (определён миксином Body, который реализован в объектах Request и Response.)

Примечание: Миксин Body имеет подобные методы для извлечения других типов контента; см. раздел Тело.

Fetch-запросы контролируются посредством директивы connect-src (Content Security Policy (en-US)), а не директивой извлекаемых ресурсов.

Установка параметров запроса

Метод fetch() может принимать второй параметр — объект init, который позволяет вам контролировать различные настройки:

// Пример отправки POST запроса:
async function postData(url = '', data = {}) {
  // Default options are marked with *
  const response = await fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, *cors, same-origin
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, *same-origin, omit
    headers: {
      'Content-Type': 'application/json'
      // 'Content-Type': 'application/x-www-form-urlencoded',
    },
    redirect: 'follow', // manual, *follow, error
    referrerPolicy: 'no-referrer', // no-referrer, *client
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
  return await response.json(); // parses JSON response into native JavaScript objects
}

postData('https://example.com/answer', { answer: 42 })
  .then((data) => {
    console.log(data); // JSON data parsed by `response.json()` call
  });

С подробным описанием функции и полным списком параметров вы можете ознакомиться на странице fetch() (en-US).

Отправка запроса с учётными данными

Чтобы браузеры могли отправлять запрос с учётными данными (даже для cross-origin запросов), добавьте credentials: 'include' в объект init, передаваемый вами в метод fetch():

fetch('https://example.com', {
  credentials: 'include'
})

Если вы хотите отправлять запрос с учётными данными только если URL принадлежит одному источнику (origin) что и вызывающий его скрипт, добавьте credentials: ‘same-origin’.

// Вызывающий скрипт принадлежит источнику 'https://example.com'

fetch('https://example.com', {
credentials: 'same-origin'
})

Напротив, чтобы быть уверенным, что учётные данные не передаются с запросом, используйте credentials: ‘omit’:

fetch('https://example.com', {
credentials: 'omit'
})

Отправка данных в формате JSON

При помощи fetch() (en-US) можно отправлять POST-запросы в формате JSON.

const url = 'https://example.com/profile';
const data = { username: 'example' };

try {
const response = await fetch(url, {
method: 'POST', // или 'PUT'
body: JSON.stringify(data), // данные могут быть 'строкой' или {объектом}!
headers: {
'Content-Type': 'application/json'
}
});
const json = await response.json();
console.log('Успех:', JSON.stringify(json));
} catch (error) {
console.error('Ошибка:', error);
}

Загрузка файла на сервер

На сервер можно загрузить файл, используя комбинацию HTML-элемента <input type="file" />, FormData() и fetch().

const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

try {
const response = await fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData
});
const result = await response.json();
console.log('Успех:', JSON.stringify(result));
} catch (error) {
console.error('Ошибка:', error);
}

Загрузка нескольких файлов на сервер

На сервер можно загрузить несколько файлов, используя комбинацию HTML-элемента <input type="file" multiple />, FormData() и fetch().

const formData = new FormData();
const photos = document.querySelector('input[type="file"][multiple]');

formData.append('title', 'Мой отпуск в Вегасе');
for (let i = 0; i < photos.files.length; i++) {
formData.append('photos', photos.files[i]);
}

try {
const response = await fetch('https://example.com/posts', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('Успех:', JSON.stringify(result));
} catch (error) {
console.error('Ошибка:', error);
}

Обработка текстового файла построчно

Фрагменты данных, получаемые из ответа, не разбиваются на строки автоматически (по крайней мере с достаточной точностью) и представляют собой не строки, а объекты Uint8Array. Если вы хотите загрузить текстовый файл и обрабатывать его по мере загрузки построчно, то на вас самих ложится груз ответственности за обработку всех упомянутых моментов. Как пример, далее представлен один из способов подобной обработки с помощью создания построчного итератора (для простоты приняты следующие допущения: текст приходит в кодировке UTF-8 и ошибки получения не обрабатываются).

async function* makeTextFileLineIterator(fileURL) {
const utf8Decoder = new TextDecoder("utf-8");
let response = await fetch(fileURL);
let reader = response.body.getReader();
let {value: chunk, done: readerDone} = await reader.read();
chunk = chunk ? utf8Decoder.decode(chunk) : "";

let re = /n|r|rn/gm;
let startIndex = 0;
let result;

for (;;) {
let result = re.exec(chunk);
if (!result) {
if (readerDone) {
break;
}
let remainder = chunk.substr(startIndex);
({value: chunk, done: readerDone} = await reader.read());
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : "");
startIndex = re.lastIndex = 0;
continue;
}
yield chunk.substring(startIndex, result.index);
startIndex = re.lastIndex;
}
if (startIndex < chunk.length) {
//последняя строка не имеет символа перевода строки в конце
yield chunk.substr(startIndex);
}
}

for await (let line of makeTextFileLineIterator(urlOfFile)) {
processLine(line);
}

Проверка успешности запроса

В методе fetch() (en-US) promise будет отклонён (reject) с TypeError, когда случится ошибка сети или не будет сконфигурирован CORS на стороне запрашиваемого сервера, хотя обычно это означает проблемы доступа или аналогичные — для примера, 404 не является сетевой ошибкой. Для достоверной проверки успешности fetch() будет включать проверку того, что promise успешен (resolved), затем проверку того, что значение свойства Response.ok (en-US) является true. Код будет выглядеть примерно так:

try {
const response = await fetch('flowers.jpg');
if (!response.ok) {
throw new Error('Ответ сети был не ok.');
}
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
} catch (error) {
console.log('Возникла проблема с вашим fetch запросом: ', error.message);
}

Составление своего объекта запроса

Вместо передачи пути ресурса, который вы хотите запросить вызовом fetch(), вы можете создать объект запроса, используя конструктор Request() (en-US), и передать его в fetch() аргументом:

const myHeaders = new Headers();

const myInit = {
method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default'
};

const myRequest = new Request('flowers.jpg', myInit);
const response = await fetch(myRequest);
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;

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

const anotherRequest = new Request(myRequest, myInit);

Довольно удобно, когда тела запроса и ответа используются единожды (прим.пер.: «are one use only»). Создание копии как показано позволяет вам использовать запрос/ответ повторно, при изменении опций init, при желании. Копия должна быть сделана до прочтения тела, а чтение тела в копии также пометит его прочитанным в исходном запросе.

Примечание: Также есть метод clone() (en-US), создающий копии. Оба метода создания копии прекратят работу с ошибкой если тело оригинального запроса или ответа уже было прочитано, но чтение тела клонированного ответа или запроса не приведёт к маркировке оригинального.

Заголовки

Интерфейс Headers (en-US) позволяет вам создать ваш собственный объект заголовков через конструктор Headers() (en-US). Объект заголовков — простая мультикарта имён-значений:

const content = 'Hello World';
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/plain');
myHeaders.append('Content-Length', content.length.toString());
myHeaders.append('X-Custom-Header', 'ProcessThisImmediately');

То же может быть достигнуто путём передачи массива массивов или литерального объекта конструктору:

const myHeaders = new Headers({
'Content-Type': 'text/plain',
'Content-Length': content.length.toString(),
'X-Custom-Header': 'ProcessThisImmediately'
});

Содержимое может быть запрошено и извлечено:

console.log(myHeaders.has("Content-Type")); // true
console.log(myHeaders.has("Set-Cookie")); // false
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");

console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.get("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]

myHeaders.delete("X-Custom-Header");
console.log(myHeaders.get("X-Custom-Header")); // [ ]

Некоторые из этих операций могут быть использованы только в ServiceWorkers (en-US), но они предоставляют более удобный API для манипуляции заголовками.

Все методы Headers выбрасывают TypeError, если имя используемого заголовка не является валидным именем HTTP Header. Операции мутации выбросят TypeError если есть защита от мутации (смотрите ниже) (прим.пер.: «if there is an immutable guard»). В противном случае они прерываются молча. Например:

const myResponse = Response.error();
try {
myResponse.headers.set('Origin', 'http://mybank.com');
} catch (e) {
console.log('Не могу притвориться банком!');
}

Хорошим вариантом использования заголовков является проверка корректности типа контента перед его обработкой. Например:

try {
const response = await fetch(myRequest);
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new TypeError("Ой, мы не получили JSON!");
}
const json = await response.json();
/_ Дальнейшая обработка JSON _/
} catch (error) {
console.log(error);
}

Защита

С тех пор как заголовки могут передаваться в запросе, приниматься в ответе и имеют различные ограничения в отношении того, какая информация может и должна быть изменена, заголовки имеют свойство guard. Это не распространяется на Web, но влияет на то, какие операции мутации доступны для объекта заголовков.

Возможные значения:

none: по умолчанию.request: защита объекта заголовков, полученного по запросу (Request.headers (en-US)).request-no-cors: защита объекта заголовков, полученного по запросу созданного с Request.mode no-cors.response: защита Headers полученных от ответа (Response.headers (en-US)).immutable: в основном, используется в ServiceWorkers; делает объект заголовков read-only.

Примечание: вы не можете добавить или установить request защищаемые Headers’ заголовок Content-Length. Аналогично, вставка Set-Cookie в заголовок ответа недопустимо: ServiceWorkers не допускают установки cookies через синтезированные ответы.

Объекты ответа

Как вы видели выше, экземпляр Response будет возвращён когда fetch() промис будет исполнен.

Свойства объекта-ответа которые чаще всего используются:

Response.status (en-US) — Целочисленное (по умолчанию 200) содержит код статуса ответа.Response.statusText (en-US) — Строка (по умолчанию»OK»), которая соответствует HTTP коду статуса.Response.ok (en-US) — как сказано ранее, это короткое свойство для упрощения проверки на то что статус ответа находится где-то между 200-299 включительно. Это свойство типа Boolean (en-US).

Они так же могут быть созданы с помощью JavaScript, но реальная польза от этого есть только при использовании сервис-воркеров (en-US), когда вы предоставляете собственный ответ на запрос с помощью метода respondWith() (en-US):

const myBody = new Blob();

addEventListener('fetch', function(event) {
// ServiceWorker перехватывает fetch
event.respondWith(
new Response(myBody, {
headers: { 'Content-Type': 'text/plain' }
})
);
});

Конструктор Response() принимает два необязательных аргумента — тело для ответа и объект init (аналогичный тому, который принимает Request() (en-US))

Примечание: Метод error() (en-US) просто возвращает ответ об ошибке. Аналогично, redirect() (en-US) возвращает ответ, приводящий к перенаправлению на указанный URL. Они также относятся только к Service Workers.

Тело

Запрос и ответ могут содержать данные тела. Тело является экземпляром любого из следующих типов:

ArrayBufferArrayBufferView (en-US) (Uint8Array и подобные)Blob/FilestringURLSearchParamsFormData

Body примесь определяет следующие методы для извлечения тела (реализованы как для Request так и для Response). Все они возвращают promise, который в конечном итоге исполняется и выводит содержимое.

arrayBuffer() (en-US)blob() (en-US)json() (en-US)text() (en-US)formData() (en-US)

Это делает использование нетекстовых данных более лёгким, чем при XMR.

В запросе можно установить параметры для отправки тела запроса:

const form = new FormData(document.getElementById('login-form'));
fetch('/login', {
method: 'POST',
body: form
});

Параметры request и response (and by extension the fetch() function), по возможности возвращают корректные типы данных. Параметр request также автоматически установит Content-Type в заголовок, если он не был установлен из словаря.

Функция обнаружения

Поддержка Fetch API может быть обнаружена путём проверки наличия Headers (en-US), Request, Response или fetch() (en-US) в области видимости Window или Worker. Для примера:

if (self.fetch) {
// запустить мой fetch запрос здесь
} else {
// Сделать что-то с XMLHttpRequest?
}

Полифил

BCD tables only load in the browser

Для того, чтобы использовать Fetch в неподдерживаемых браузерах, существует Fetch Polyfill , который воссоздаёт функциональность для не поддерживающих браузеров.

СпецификацииSpecification Status CommentFetch Живой стандарт Initial definitionСовместимость браузера

Смотрите такжеServiceWorker APIHTTP access control (CORS)HTTPFetch polyfillFetch examples on Github`

Начало: «JavaScript: простой пример работы с методом fetch».

В прошлом посте я разбирал, как заставить работать пример с встроенным методом fetch из подраздела 3.1 «Fetch» третьего раздела («Сетевые запросы») третьей части («Тематические разделы») учебника по JavaScript.

В итоге у меня получился такой код:

(async () => {
    let url = "https://api.github.com";
    let response = await fetch(url);

    if (response.ok) { // если HTTP-статус в диапазоне 200-299
        // получаем тело ответа
        let json = await response.json();
        console.log(json);
    } else {
        alert("Ошибка HTTP: " + response.status);
    }
})();

Можно было заметить, что, несмотря на вроде бы наличие ветки с обработкой ошибок, скрипт всё равно при некоторых ошибках (эти ошибки в консоли разработчика содержат в своём названии слово «Uncaught», что по-русски означает «Непойманная» [в смысле «непойманная ошибка»]) прекращает работу. (Я писал об этом отдельный пост: «JavaScript: слово Uncaught в названии ошибки».)

На самом деле, в вышеприведенном коде ветка с обработкой ошибок обрабатывает только ошибки, которые возникнут на веб-сервере при обработке запроса, который мы в данном коде посылаем на веб-сервер (ошибкой наш скрипт считает все ответы веб-сервера с кодом, выходящим за пределы диапазона 200-299).

Ну а непойманная ошибка в данном случае может возникнуть до того, как исполнение скрипта дойдет до ветки с обработкой ошибок, при выполнении одной из двух асинхронных задач. Либо тут (выполнение первой асинхронной задачи):

    let response = await fetch(url);

либо тут (выполнение второй асинхронной задачи):

        let json = await response.json();

Если случается непойманная скриптом ошибка, скрипт прекращает работу. При обучении или отладке мы временно можем себе это позволить, так как у нас при этом есть более важные задачи: при обучении более важная задача — это понять иллюстрируемые данным примером кода некие принципы программирования или решение каких-то задач (например, научиться работать с встроенным методом fetch, как в обсуждаемой статье учебника); а при отладке более важная задача — заставить код сначала хоть как-то работать.

Но в скрипте, как окончательном продукте, мы не можем себе позволить непойманных ошибок. Ведь в скрипте при возникновении непойманной ошибки могут еще быть невыполненные инструкции, то есть скрипт на момент такой ошибки, возможно, еще не успел сделать какие-то важные действия. Пользователю при этом никаких ошибок не показывается, скрипт просто «умирает» (если пользователь продвинутый, он, конечно, может открыть консоль разработчика в браузере [если скрипт был запущен в браузерном окружении] и увидеть сообщение о непойманной ошибке). Так что все возможные непойманные ошибки должны быть отловлены.

Как это сделать в данном случае? Изменим код (я отметил изменение красным цветом):

(async () => {
    let url = "https://api.github.com";
    let response = await fetch(url);

    if (response.ok) { // если HTTP-статус в диапазоне 200-299
        // получаем тело ответа
        let json = await response.json();
        console.log(json);
    } else {
        alert("Ошибка HTTP: " + response.status);
    }
})().catch(error => alert(error));

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

Подробнее об обработке ошибок при работе с промисами можно почитать следующие статьи учебника из первой части учебника:

Потребители: then, catch, finally
https://learn.javascript.ru/promise-basics#potrebiteli-then-catch-finally

Промисы: обработка ошибок
https://learn.javascript.ru/promise-error-handling

Обработка ошибок при использовании синтаксиса «async/await»
https://learn.javascript.ru/async-await#obrabotka-oshibok

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

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

  • Js как изменить текст тега
  • Js как изменить стиль у класса
  • Js как изменить атрибут элемента
  • Js type error is not a function
  • Js throw error with object

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

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