Fetch get error

A quick example of how to handle both network errors and HTTP errors (4xx or 5xx) for fetch requests in a single catch block

This is a quick example of how to handle both network errors and HTTP errors (4xx or 5xx) for Fetch API (fetch()) requests in a single catch() block.

GET request with error handling

This sends an HTTP GET request to an invalid URL on the Reqres api which is a fake online REST api used for testing, then writes the error message to the parent of the #get-request-error-handling .total element and logs the error to the console.

Error Handling

The fetch() function will automatically throw an error for network errors but not for HTTP errors such as 4xx or 5xx responses. For HTTP errors we can check the response.ok property to see if the request failed and reject the promise ourselves by calling return Promise.reject(error);. This approach means that both types of failed requests — network errors and http errors — can be handled by a single catch() block instead of needing two separate pieces of error handling code.

JSON Check

The fetch .then() callback is passed the HTTP response object when the request is completed, the function checks if the response type is JSON before parsing the response body with the response.json() method, because calling response.json() will cause an error if the response doesn’t contain JSON data.

// Fetch GET request with error handling
const element = document.querySelector('#get-request .result');
fetch('https://reqres.in/invalid-url')
    .then(async response => {
        const isJson = response.headers.get('content-type')?.includes('application/json');
        const data = isJson ? await response.json() : null;

        // check for error response
        if (!response.ok) {
            // get error message from body or default to response status
            const error = (data && data.message) || response.status;
            return Promise.reject(error);
        }

        element.innerHTML = JSON.stringify(data, null, 4);
    })
    .catch(error => {
        element.parentElement.innerHTML = `Error: ${error}`;
        console.error('There was an error!', error);
    });

Run the example Fetch GET request on StackBlitz at https://stackblitz.com/edit/fetch-error-handling-http-and-network-errors

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter, Facebook or GitHub to be notified when I post new content.

  • Subscribe on YouTube at https://www.youtube.com/JasonWatmore
  • Follow me on Twitter at https://twitter.com/jason_watmore
  • Follow me on Facebook at https://www.facebook.com/JasonWatmoreBlog
  • Follow me on GitHub at https://github.com/cornflourblue
  • Feed formats available:
    RSS,
    Atom,
    JSON

Other than coding…

I’m currently attempting to travel around Australia by motorcycle with my wife Tina on a pair of Royal Enfield Himalayans. You can follow our adventures on YouTube, Instagram and Facebook.

  • Subscribe on YouTube at https://www.youtube.com/TinaAndJason
  • Follow us on Instagram at https://www.instagram.com/tinaandjason
  • Follow us on Facebook at https://www.facebook.com/TinaAndJasonVlog
  • Visit our website at https://tinaandjason.com.au

Need Some Fetch Help?

Search fiverr to find help quickly from experienced Fetch developers.



This documents the polyfillable parts of the
WHATWG Fetch standard.
See Caveats for notable exceptions.

Usage synopsis (use the argument links to find out more):

fetch(url, options).then(function(response) {
  // handle HTTP response
}, function(error) {
  // handle network error
})

More comprehensive usage example:

fetch(url, {
  method: "POST",
  body: JSON.stringify(data),
  headers: {
    "Content-Type": "application/json"
  },
  credentials: "same-origin"
}).then(function(response) {
  response.status     //=> number 100–599
  response.statusText //=> String
  response.headers    //=> Headers
  response.url        //=> String

  return response.text()
}, function(error) {
  error.message //=> String
})

Request

Synopsis:
new Request(url, options)

Request represents a HTTP request to be performed via fetch().
Typically a Request doesn’t need to be constructed manually,
as it’s instantiated internally when fetch() is called.

URL (Request or string)

The URL of the resource which is being fetched.
Typically this is an absolute URL without the host component, e.g. "/path".
If the URL has the host of another site, the request is performed in accordance to
CORS.

Any non-Request object will be converted to a String, making it possible to
pass an instance of URL,
for example.

A request can be initialized using another instance of Request in place of the URL.
In that case, the URL and other options are inherited from the provided Request object.

Options

  • method (String) — HTTP request method. Default: "GET"
  • body (String, body types) — HTTP request body
  • headers (Object, Headers) — Default: {}
  • credentials (String) — Authentication credentials mode. Default: "omit"
    • "omit" — don’t include authentication credentials (e.g. cookies) in the request
    • "same-origin" — include credentials in requests to the same site
    • "include" — include credentials in requests to all sites

Body types

Class Default Content-Type
String text/plain;charset=UTF-8
URLSearchParams application/x-www-form-urlencoded;charset=UTF-8
FormData multipart/form-data
Blob inherited from the blob.type property
ArrayBuffer
TypedArray
DataView

Other data structures need to be encoded beforehand as one of the above types.
For instance, JSON.stringify(data) can be used to serialize a
data structure into a JSON string.

Note that HTTP servers often require that requests that are posted with a body
also specify their type via a Content-Type request header.

Response

Response represents a HTTP response from the server.
Typically a Response is not constructed manually,
but is available as argument to the resolved promise callback.

Properties

  • status (number) — HTTP response code in the 100–599 range
  • statusText (String) — Status text as reported by the server, e.g. «Unauthorized»
  • ok (boolean) — True if status is HTTP 2xx
  • headers (Headers)
  • url (String)

Body methods

Each of the methods to access the response body returns a Promise that will be
resolved when the associated data type is ready.

  • text() — yields the response text as String
  • json() — yields the result of JSON.parse(responseText)
  • blob() — yields a Blob
  • arrayBuffer() — yields an ArrayBuffer
  • formData() — yields FormData that can be forwarded to another request

Other response methods

  • clone()
  • Response.error()
  • Response.redirect()

Synopsis:
new Headers(hash)

Headers represents a set of request/response HTTP headers.
It allows for case-insensitive lookup of header by name,
as well as merging multiple values of a single header.

Methods

  • has(name) (boolean)
  • get(name) (String)
  • set(name, value)
  • append(name, value)
  • delete(name)
  • forEach(function(value, name){ ... }, [thisContext])

Error

If there is a network error or another reason why the HTTP request couldn’t be fulfilled,
the fetch() promise will be rejected with a reference to that error.

Note that the promise won’t be rejected in case of HTTP 4xx or 5xx server responses.
The promise will be resolved just as it would be for HTTP 2xx.
Inspect the response.status number within the resolved callback to add conditional handling of server errors to your code.


fetch(...).then(function(response) {
  if (response.ok) {
    return response
  } else {
    var error = new Error(response.statusText)
    error.response = response
    throw error
  }
})

Caveats

The whatwg-fetch polyfill isn’t
able nor does it aim to implement the entire WHATWG Fetch standard, since some
of the standard features would be non-trivial or otherwise unfeasible to
implement. Notable examples include:

  • Inability to set the redirect mode
  • Inability to change the cache directive
  • Inability to disable same-origin cookies

The Fetch API provides a JavaScript interface for accessing and manipulating parts of the protocol, such as requests and responses. It also provides a global fetch() method that provides an easy, logical way to fetch resources asynchronously across the network.

This kind of functionality was previously achieved using XMLHttpRequest. Fetch provides a better alternative that can be easily used by other technologies such as Service Workers. Fetch also provides a single logical place to define other HTTP-related concepts such as CORS and extensions to HTTP.

The fetch specification differs from jQuery.ajax() in the following significant ways:

  • The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, as soon as the server responds with headers, the Promise will resolve normally (with the ok property of the response set to false if the response isn’t in the range 200–299), and it will only reject on network failure or if anything prevented the request from completing.
  • Unless fetch() is called with the credentials option set to include, fetch():
    • won’t send cookies in cross-origin requests
    • won’t set any cookies sent back in cross-origin responses
    • As of August 2018, the default credentials policy changed to same-origin. Firefox was also modified in version 61.0b13)

A basic fetch request is really simple to set up. Have a look at the following code:

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

Here we are fetching a JSON file across the network and printing it to the console. The simplest use of fetch() takes one argument — the path to the resource you want to fetch — and does not directly return the JSON response body but instead returns a promise that resolves with a Response object.

The Response object, in turn, does not directly contain the actual JSON response body but is instead a representation of the entire HTTP response. So, to extract the JSON body content from the Response object, we use the json() method, which returns a second promise that resolves with the result of parsing the response body text as JSON.

Note: See the Body section for similar methods to extract other types of body content.

Fetch requests are controlled by the connect-src directive of Content Security Policy rather than the directive of the resources it’s retrieving.

Supplying request options

The fetch() method can optionally accept a second parameter, an init object that allows you to control a number of different settings:

See fetch() for the full options available, and more details.

// Example POST method implementation:
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, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: JSON.stringify(data) // body data type must match "Content-Type" header
  });
  return 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 `data.json()` call
  });

Note that mode: "no-cors" only allows a limited set of headers in the request:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type with a value of application/x-www-form-urlencoded, multipart/form-data, or text/plain

Sending a request with credentials included

To cause browsers to send a request with credentials included on both same-origin and cross-origin calls, add credentials: 'include' to the init object you pass to the fetch() method.

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

Note: Access-Control-Allow-Origin is prohibited from using a wildcard for requests with credentials: 'include'. In such cases, the exact origin must be provided; even if you are using a CORS unblocker extension, the requests will still fail.

Note: Browsers should not send credentials in preflight requests irrespective of this setting. For more information see: CORS > Requests with credentials.

If you only want to send credentials if the request URL is on the same origin as the calling script, add credentials: 'same-origin'.

// The calling script is on the origin 'https://example.com'

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

To instead ensure browsers don’t include credentials in the request, use credentials: 'omit'.

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

Uploading JSON data

Use fetch() to POST JSON-encoded data.

const data = { username: 'example' };

fetch('https://example.com/profile', {
  method: 'POST', // or 'PUT'
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
})
  .then((response) => response.json())
  .then((data) => {
    console.log('Success:', data);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Uploading a file

Files can be uploaded using an HTML <input type="file" /> input element, FormData() and fetch().

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

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

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
  .then((response) => response.json())
  .then((result) => {
    console.log('Success:', result);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Uploading multiple files

Files can be uploaded using an HTML <input type="file" multiple /> input element, FormData() and fetch().

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

formData.append('title', 'My Vegas Vacation');
let i = 0;
for (const photo of photos.files) {
  formData.append(`photos_${i}`, photo);
  i++;
}

fetch('https://example.com/posts', {
  method: 'POST',
  body: formData,
})
  .then((response) => response.json())
  .then((result) => {
    console.log('Success:', result);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Processing a text file line by line

The chunks that are read from a response are not broken neatly at line boundaries and are Uint8Arrays, not strings. If you want to fetch a text file and process it line by line, it is up to you to handle these complications. The following example shows one way to do this by creating a line iterator (for simplicity, it assumes the text is UTF-8, and doesn’t handle fetch errors).

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

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

  while (true) {
    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) {
    // Last line didn't end in a newline char
    yield chunk.substr(startIndex);
  }
}

async function run() {
  for await (const line of makeTextFileLineIterator(urlOfFile)) {
    processLine(line);
  }
}

run();

Checking that the fetch was successful

A fetch() promise will reject with a TypeError when a network error is encountered or CORS is misconfigured on the server-side, although this usually means permission issues or similar — a 404 does not constitute a network error, for example. An accurate check for a successful fetch() would include checking that the promise resolved, then checking that the Response.ok property has a value of true. The code would look something like this:

fetch('flowers.jpg')
  .then((response) => {
    if (!response.ok) {
      throw new Error('Network response was not OK');
    }
    return response.blob();
  })
  .then((myBlob) => {
    myImage.src = URL.createObjectURL(myBlob);
  })
  .catch((error) => {
    console.error('There has been a problem with your fetch operation:', error);
  });

Supplying your own request object

Instead of passing a path to the resource you want to request into the fetch() call, you can create a request object using the Request() constructor, and pass that in as a fetch() method argument:

const myHeaders = new Headers();

const myRequest = new Request('flowers.jpg', {
  method: 'GET',
  headers: myHeaders,
  mode: 'cors',
  cache: 'default',
});

fetch(myRequest)
  .then((response) => response.blob())
  .then((myBlob) => {
    myImage.src = URL.createObjectURL(myBlob);
  });

Request() accepts exactly the same parameters as the fetch() method. You can even pass in an existing request object to create a copy of it:

const anotherRequest = new Request(myRequest, myInit);

This is pretty useful, as request and response bodies can only be used once.
Making a copy like this allows you to effectively use the request/response again while varying the init options if desired.
The copy must be made before the body is read.

Note: There is also a clone() method that creates a copy. Both methods of creating a copy will fail if the body of the original request or response has already been read, but reading the body of a cloned response or request will not cause it to be marked as read in the original.

The Headers interface allows you to create your own headers object via the Headers() constructor. A headers object is a simple multi-map of names to values:

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');

The same can be achieved by passing an array of arrays or an object literal to the constructor:

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

The contents can be queried and retrieved:

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')); // null

Some of these operations are only useful in ServiceWorkers, but they provide a much nicer API for manipulating headers.

All of the Headers methods throw a TypeError if a header name is used that is not a valid HTTP Header name. The mutation operations will throw a TypeError if there is an immutable guard (see below). Otherwise, they fail silently. For example:

const myResponse = Response.error();
try {
  myResponse.headers.set('Origin', 'http://mybank.com');
} catch (e) {
  console.log('Cannot pretend to be a bank!');
}

A good use case for headers is checking whether the content type is correct before you process it further. For example:

fetch(myRequest)
  .then((response) => {
     const contentType = response.headers.get('content-type');
     if (!contentType || !contentType.includes('application/json')) {
       throw new TypeError("Oops, we haven't got JSON!");
     }
     return response.json();
  })
  .then((data) => {
      /* process your data further */
  })
  .catch((error) => console.error(error));

Guard

Since headers can be sent in requests and received in responses, and have various limitations about what information can and should be mutable, headers’ objects have a guard property. This is not exposed to the Web, but it affects which mutation operations are allowed on the headers object.

Possible guard values are:

  • none: default.
  • request: guard for a headers object obtained from a request (Request.headers).
  • request-no-cors: guard for a headers object obtained from a request created with Request.mode no-cors.
  • response: guard for a headers object obtained from a response (Response.headers).
  • immutable: guard that renders a headers object read-only; mostly used for ServiceWorkers.

Note: You may not append or set the Content-Length header on a guarded headers object for a response. Similarly, inserting Set-Cookie into a response header is not allowed: ServiceWorkers are not allowed to set cookies via synthesized responses.

Response objects

As you have seen above, Response instances are returned when fetch() promises are resolved.

The most common response properties you’ll use are:

  • Response.status — An integer (default value 200) containing the response status code.
  • Response.statusText — A string (default value «»), which corresponds to the HTTP status code message. Note that HTTP/2 does not support status messages.
  • Response.ok — seen in use above, this is a shorthand for checking that status is in the range 200-299 inclusive. This returns a boolean value.

They can also be created programmatically via JavaScript, but this is only really useful in ServiceWorkers, when you are providing a custom response to a received request using a respondWith() method:

const myBody = new Blob();

addEventListener('fetch', (event) => {
  // ServiceWorker intercepting a fetch
  event.respondWith(
    new Response(myBody, {
      headers: { 'Content-Type': 'text/plain' }
    })
  );
});

The Response() constructor takes two optional arguments — a body for the response, and an init object (similar to the one that Request() accepts.)

Note: The static method error() returns an error response. Similarly, redirect() returns a response resulting in a redirect to a specified URL. These are also only relevant to Service Workers.

Body

Both requests and responses may contain body data. A body is an instance of any of the following types:

  • ArrayBuffer
  • TypedArray (Uint8Array and friends)
  • DataView
  • Blob
  • File
  • String, or a string literal
  • URLSearchParams
  • FormData

The Request and Response interfaces share the following methods to extract a body. These all return a promise that is eventually resolved with the actual content.

  • Request.arrayBuffer() / Response.arrayBuffer()
  • Request.blob() / Response.blob()
  • Request.formData() / Response.formData()
  • Request.json() / Response.json()
  • Request.text() / Response.text()

This makes usage of non-textual data much easier than it was with XHR.

Request bodies can be set by passing body parameters:

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

Both request and response (and by extension the fetch() function), will try to intelligently determine the content type. A request will also automatically set a Content-Type header if none is set in the dictionary.

Feature detection

Fetch API support can be detected by checking for the existence of Headers, Request, Response or fetch() on the Window or Worker scope. For example:

if (window.fetch) {
  // run my fetch request here
} else {
  // do something with XMLHttpRequest?
}

Specifications

Specification
Fetch Standard
# fetch-method

Browser compatibility

BCD tables only load in the browser

See also

Время прочтения
5 мин

Просмотры 269K

Прощай, XMLHttpRequest!

fetch() позволяет вам делать запросы, схожие с XMLHttpRequest (XHR). Основное отличие заключается в том, что Fetch API использует Promises (Обещания), которые позволяют использовать более простое и чистое API, избегать катастрофического количества callback’ов и необходимости помнить API для XMLHttpRequest.

Fetch API стал доступен пользователям вместе с Service Worker’ами в global скоупе в Chrome 40, однако уже в версии 42 он станет доступен в скоупе window. Разумеется, для всех остальных браузеров, которые пока ещё не поддерживают fetch существует полифил от GitHub, который доступен уже сегодня.

Простой Fetch запрос

Давайте начнём со сравнения простого примера, реализованного с XMLHttpRequest и fetch. Всё, что мы будем делать в этом примере — сделаем запрос на URL, получим ответ и распарсим его как JSON.

XMLHttpRequest

Пример с XMLHttpRequest потребует от нас установить два обработчика событий на success и error, а так же вызвать два метода: open() и send(). Пример из MDN документации:

function reqListener() {  
  var data = JSON.parse(this.responseText);  
  console.log(data);  
}

function reqError(err) {  
  console.log('Fetch Error :-S', err);  
}

var oReq = new XMLHttpRequest();  
oReq.onload = reqListener;  
oReq.onerror = reqError;  
oReq.open('get', './api/some.json', true);  
oReq.send();

Fetch

Наш fetch запрос будет выглядеть так:

fetch('./api/some.json')  
  .then(  
    function(response) {  
      if (response.status !== 200) {  
        console.log('Looks like there was a problem. Status Code: ' +  
          response.status);  
        return;  
      }

      // Examine the text in the response  
      response.json().then(function(data) {  
        console.log(data);  
      });  
    }  
  )  
  .catch(function(err) {  
    console.log('Fetch Error :-S', err);  
  });

Первым делом мы проверяем статус ответа и проверяем, успешно ли выполнился запрос (ожидаем 200 статус). Если всё хорошо, то парсим ответ как JSON.

Ответом fetch() является Stream-объект. Это означает, что в результате вызова метода json() мы получим Promise, т.к. чтение из подобного объекта является асинхронным.

Метаданные ответа

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

fetch('users.json').then(function(response) {  
    console.log(response.headers.get('Content-Type'));  
    console.log(response.headers.get('Date'));

    console.log(response.status);  
    console.log(response.statusText);  
    console.log(response.type);  
    console.log(response.url);  
});

Типы ответа

Когда мы делаем fetch-запрос, ответу будет дан тип «basic», «cors» или «opaque». Эти «типы» указывают на то, с какого ресурса пришли данные и могут быть использованы для того, чтобы определить процесс обработки данных.

Когда запрос сделан на ресурс, находящимся на том же origin (имеется ввиду, что запрос выполняется в рамках одного сайта. прим. пер.), ответ будет содержать тип «базовый» и для такого запроса не будет никаких ограничений.

Если запрос сделан с одного origin’а на другой (кроссдоменный запрос), который, в свою очередь, вернул CORS заголовки, тогда типом будет являться «cors». Объекты с типами «cors» и «basic» почти идентичны, однако «cors» несколько ограничивает метаданные, к которым может быть получен доступ до «Cache-Control», «Content-Language», «Content-Type», «Expires», «Last-Modified», и «Pragma».

Что касается «opaque» — то он приходит в случаях, когда выполняется CORS запрос, но удаленный ресурс не возвращает CORS заголовки. Данный тип запроса не предоставляет доступ данным или заголовку статуса, поэтому мы не имеем возможности судить о результате выполнения запроса. В рамках текущей имплементации fetch() не представляется возможности выполнять CORS запросы из скоупа window, и вот здесь написано почему. Эта функциональность должна быть добавлена, как только Cache API станет доступным из объекта window.

Вы можете определить ожидаемый режим запроса, тем самым фильтруя результаты запросов с неподходящим типом. Режим запроса может быть установлен на следующий:

— “same-origin” успешно выполняется только для запросов на тот же самый origin, все остальные запросы будут отклонены.
— “cors” работает так же, как «same-origin» + добавляет возможность создавать запросы к сторонним сайтам, если они возвращают соответствующие CORS заголовки.
— “cors-with-forced-preflight” работает так же, как «cors», но перед запросом всегда отсылает тестовый запрос на проверку.
— “no-cors” используется, когда необходимо выполнить запрос к origin, который не отсылает CORS заголовки и результатом выполнения является объект с типом «opaque». Как говорилось выше, в данный момент это невозможно в скоупе window.

Чтобы определить режим запроса, добавьте объект опций вторым параметром к запросу и установите «mode» в этом объекте:

fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})  
  .then(function(response) {  
    return response.text();  
  })  
  .then(function(text) {  
    console.log('Request successful', text);  
  })  
  .catch(function(error) {  
    log('Request failed', error)  
  });

Цепочки Promises

Одной из прекрасных особенностей Promise’ов является возможность группировать их в цепочки. Если говорить о них в скоупе fetch(), то они позволяют нам «шарить» логику между запросами.

Если вы работаете с JSON API, вам потребуется проверить статус и распарсить JSON для каждого ответа. Вы можете упростить свой код, определив парсинг статуса и JSON как раздельные функции, которые вернут Promise’ы. Вам останется подумать только об обработке самих данных и, разумеется, исключений.

function status(response) {  
  if (response.status >= 200 && response.status < 300) {  
    return Promise.resolve(response)  
  } else {  
    return Promise.reject(new Error(response.statusText))  
  }  
}

function json(response) {  
  return response.json()  
}

fetch('users.json')  
  .then(status)  
  .then(json)  
  .then(function(data) {  
    console.log('Request succeeded with JSON response', data);  
  }).catch(function(error) {  
    console.log('Request failed', error);  
  });

Мы определяем функцию, которая проверяет response.status и возвращает результат: Promise.resolve() или Promise.reject(). Это первый вызванный метод в нашей цепочке, и если он успешно завершается(Promise.resolve()), то вызывается следующий за ним метод — fetch(), который, в свою очередь, опять возвращает Promise от response.json(). После этого вызова, в случае удачного выполнения, у нас будет готовый JSON объект. Если парсинг провалится, Promise будет отменен и сработает условие возникновения исключения.

Но самое лучшее здесь — это возможность переиспользовать такой код для всех fetch-запросов в приложении. Такой код проще поддерживать, читать и тестировать.

POST запрос

Уже давно никого не удивишь необходимостью использовать POST метод с передачей параметров в «теле» запроса для работы с API.
Чтобы осуществить такой запрос, мы должны указать соответствующие параметры в объекте настроек fetch():

fetch(url, {  
    method: 'post',  
    headers: {  
      "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"  
    },  
    body: 'foo=bar&lorem=ipsum'  
  })
  .then(json)  
  .then(function (data) {  
    console.log('Request succeeded with JSON response', data);  
  })  
  .catch(function (error) {  
    console.log('Request failed', error);  
  });

Посылаем учётные данные через Fetch-запрос

Если вы хотите отправить запрос с каким-либо учётными данными (например, с cookie), вам следует установить `credentials` в опциях запроса на «include»:

fetch(url, {  
  credentials: 'include'  
})

FAQ

Могу ли я отменить fetch() запрос?
В настоящий момент это невозможно, но это активно обсуждается на GitHub

Существует ли полифил?
Да

Почему «no-cors» реализован для service workers, но не для window?
Это было сделано из соображений безопасности. Подробнее можно ознакомиться здесь.

Quiz: What does this call to the web’s new fetch() API do?

fetch("http://httpstat.us/500")
    .then(function() {
        console.log("ok");
    }).catch(function() {
        console.log("error");
    });

If you’re like me, you might assume this code logs “error” when run—but it actually logs “ok”.

This expectation probably comes from years of jQuery development, as jQuery’s ajax() method invokes its fail handler when the response contains a failed HTTP status code. For example, the code below logs “error” when run:

$.ajax("http://httpstat.us/500")
    .done(function() {
        console.log("ok");
    }).fail(function() {
        console.log("error");
    });

Why does fetch() work this way?

Per MDN, the fetch() API only rejects a promise when a “network error is encountered, although this usually means permissions issues or similar.” Basically fetch() will only reject a promise if the user is offline, or some unlikely networking error occurs, such a DNS lookup failure.

The good is news is fetch provides a simple ok flag that indicates whether an HTTP response’s status code is in the successful range or not. For instance the following code logs “Error: Internal Server Error(…)”:

fetch("http://httpstat.us/500")
    .then(function(response) {
        if (!response.ok) {
            throw Error(response.statusText);
        }
        return response;
    }).then(function(response) {
        console.log("ok");
    }).catch(function(error) {
        console.log(error);
    });

To keep this code DRY and reusable, you probably want to create a generic error handling function you can use for all of your fetch() calls. The following code refactors the error handling into a handleErrors() function:

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    return response;
}

fetch("http://httpstat.us/500")
    .then(handleErrors)
    .then(function(response) {
        console.log("ok");
    }).catch(function(error) {
        console.log(error);
    });

For added fun you can use ES6 arrow functions to make the callback formatting a little less verbose:

function handleErrors(response) {
    if (!response.ok) {
        throw Error(response.statusText);
    }
    return response;
}
fetch("http://httpstat.us/500")
    .then(handleErrors)
    .then(response => console.log("ok") )
    .catch(error => console.log(error) );

Parting thoughts

Although I still don’t like fetch()’s lack of rejecting failed HTTP status codes, over time fetch()’s behavior has grown on me—mostly because it gives me more control over how I handle individual problems. Plus, the composable nature of fetch() makes it fairly trivial to manually handle errors without adding a bunch of verbose code.

Overall I think it’s worth taking few minutes to play with fetch(), even if it’s just to see what you think. It’s certainly a far more readable alternative to XMLHttpRequest. If you happen to be building NativeScript apps, you might not know that you can use fetch() today without any need for a polyfill or fallback. And something about using fetch() to perform HTTP requests in native Android and iOS apps is just plain cool :)

This article was updated on September 15th, 2015 to use a simpler handleErrors() function based on a comment from Jake Archibald.

The Fetch API is a promise-based JavaScript API for making asynchronous HTTP requests in the browser similar to XMLHttpRequest (XHR). Unlike XHR, it is a simple and clean API that uses promises to provide a powerful and flexible feature set to fetch resources from the server.

Fetch API is pretty much standardized and is supported by all modern browsers except IE. If you need all browsers, including IE, add a polyfill released by GitHub to your project.

How to use Fetch API?

Using Fetch API is really simple. Just pass the URL, the path to the resource you want to fetch, to the fetch() method:

fetch('/js/users.json')
  .then(response => {
    // handle response data
  })
  .catch(err => {
    // handle errors
  })

We pass the path for the resource we want to retrieve as a parameter to fetch(). It returns a promise that sends the response to then() when it is fulfilled. The catch() method intercepts errors if the request fails to complete due to network failure or other reasons.

Make GET request using Fetch API

By default, the Fetch API uses the GET method for asynchronous requests. Let’s use the Reqres REST API to retrieve a list of users using a GET request:

fetch('https://reqres.in/api/users')
  .then(res => res.json())
  .then(res => {
    res.data.map(user => {
      console.log(`${user.id}: ${user.first_name} ${user.last_name}`)
    })
  })

The above request prints the following on the console:

1: George Bluth
2: Janet Weaver
3: Emma Wong

Calling the fetch() method returns a promise. The response returned by the promise is a stream object. When we call the json() method on the stream object, it returns another promise. The call to the json() method indicates that we are expecting a JSON response. For an XML response, you should use the text() method.

Make POST request using Fetch API

Just like Axios, Fetch also allows us to use any other HTTP method in the request: POST, PUT, DELETE, HEAD, and OPTIONS. All you need to do is set the method and body parameters in the fetch() options:

const user = {
  first_name: 'John',
  last_name: 'Lilly',
  job_title: 'Software Engineer'
}

const options = {
  method: 'POST',
  body: JSON.stringify(user),
  headers: {
    'Content-Type': 'application/json'
  }
}

fetch('https://reqres.in/api/users', options)
  .then(res => res.json())
  .then(res => console.log(res))

The Reqres API returns the body data back with an ID and created timestamp attached:

{  
   "first_name":"John",
   "last_name":"Lilly",
   "job_title":"Software Engineer",
   "id":"482",
   "createdAt":"2019-05-12T15:09:13.140Z"
}

Make DELETE request using Fetch API

The DELETE request looks very similar to the POST request, except body is not required:

const options = {
  method: 'DELETE',
  headers: {
    'Content-Type': 'application/json'
  }
}

fetch('https://reqres.in/api/users/2', options)
  .then(res => {
    if (res.ok) {
      return Promise.resolve('User deleted.')
    } else {
      return Promise.reject('An error occurred.')
    }
  })
  .then(res => console.log(res))

Error handling in Fetch API

Since the fetch() method returns a promise, error handling is easy. We can use the catch() method of the promise object to intercept any error thrown during the execution of the request.

However, no error will be thrown if the request hits the server and comes back, regardless of the server’s response. The promise returned by fetch() does not reject HTTP errors, even if the HTTP response code is 404 or 500.

Fortunately, you can use the ok property of the response object to check whether the request was successful or not:

fetch('https://reqres.in/api/users/22') // 404 Error
  .then(res => {
    if (res.ok) {
      return res.json()
    } else {
      return Promise.reject(res.status)
    }
  })
  .then(res => console.log(res))
  .catch(err => console.log(`Error with message: ${err}`))

Request headers (like Accept, Content-Type, User-Agent, Referer, etc.) are essential for any HTTP request. The Fetch API’s Headers object allows us to set, remove, or retrieve HTTP request headers.

We can create a header object using the Headers() constructor and then use the append, has, get, set, and delete methods to modify request headers:

// create an empty `Headers` object
const headers = new Headers()

// add headers
headers.append('Content-Type', 'text/plain')
headers.append('Accept', 'application/json')

// add custom headers
headers.append('X-AT-Platform', 'Desktop')
headers.append('X-AT-Source', 'Google Search')

// check if the header exists
headers.has('Accept') // true

// get headers
headers.get('Accept') // application/json
headers.get('X-AT-Source') // Google Search

// update header value
headers.set('Content-Type', 'application/json')

// remove headers
headers.delete('Content-Type')
headers.delete('X-AT-Platform')

We can also pass an array of arrays or an object literal to the constructor to create a header object:

// passing an object literal
const headers = new Headers({
  'Content-Type': 'application/json',
  Accept: 'application/json'
})

// OR

// passing an array of arrays
const headers = new Headers([
  ['Content-Type', 'application/json'],
  ['Accept', 'application/json']
])

To add headers to the request, create a Request instance and pass it to the fetch() method instead of the URL:

const request = new Request('https://reqres.in/api/users', {
  headers: headers
})

fetch(request)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error('Error:', err))

Fetch API request object

The Request object represents a resource request and can be created by calling the Request() constructor:

const request = new Request('https://reqres.in/api/users')

The Request object also accepts a URL object:

const url = new URL('https://reqres.in/api/users')
const request = new Request(url)

By passing a Request object to fetch(), you can easily customize the request properties:

  • method — An HTTP method like GET, POST, PUT, DELETE, HEAD
  • url — The URL to the request, a string or a URL object
  • headers — a Headers object for request headers
  • referrer — referrer of the request (e.g., client)
  • mode — The mode for cross-origin requests (e.g., cors, no-cors, same-origin)
  • credentials — Should cookies and HTTP-Authorization headers go with the request? (e.g., include, omit, same-origin)
  • redirect — The redirect mode of the request (e.g., follow, error, manual)
  • integrity — The subresource integrity value of the request
  • cache — The cache mode of the request (e.g, default, reload, no-cache)

Let us create a Request object with some customized properties and body content to make a POST request:

const user = {
  first_name: 'John',
  last_name: 'Lilly',
  job_title: 'Software Engineer'
}

const headers = new Headers({
  'Content-Type': 'application/json',
  Accept: 'application/json'
})

const request = new Request('https://reqres.in/api/users', {
  method: 'POST',
  headers: headers,
  redirect: 'follow',
  mode: 'cors',
  body: JSON.stringify(user)
})

fetch(request)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error('Error:', err))

Only the first argument, the URL, is required. All these properties are read-only. You can not change their value once the request object is created. The Fetch API does not strictly require a Request object. The object literal, which we pass to the fetch() method, acts like a Request object:

fetch('https://reqres.in/api/users', {
  method: 'POST',
  headers: headers,
  redirect: 'follow',
  mode: 'cors',
  body: JSON.stringify(user)
})
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error('Error:', err))

Fetch API response object

The Response object returned by the fetch() method contains the information about the request and the response of the network request, including headers, status code, and status message:

fetch('https://reqres.in/api/users').then(res => {
  // get response headers
  console.log(res.headers.get('content-type))
  console.log(res.headers.get('expires'))

  // HTTP response status code
  console.log(res.status)

  // shorthand for `status` between 200 and 299
  console.log(res.ok)

  // status message of the response e.g. `OK`
  console.log(res.statusText)

  // check if there was a redirect
  console.log(res.redirected)

  // get the response type (e.g., `basic`, `cors`)
  console.log(res.type)

  // the full path of the resource
  console.log(res.url)
})

The response body is accessible through the following methods:

  • json() returns the body as a JSON object
  • text() returns the body as a string
  • blob() returns the body as a Blob object
  • formData() returns the body as a FormData object
  • arrayBuffer() returns the body as an ArrayBuffer object

All these methods return a promise. Here is an example of text() method:

fetch('https://reqres.in/api/unknown/2')
  .then(res => res.text())
  .then(res => console.log(res))

The output of the above network call will be a JSON string:

'{"data":{"id":2,"name":"fuchsia rose","year":2001,"color":"#C74375","pantone_value":"17-2031"}}'

Send cookies using Fetch API

When you use Fetch to get a resource, the request does not contain credentials such as cookies. If you want to send cookies, you have to explicitly enable credentials like the below:

fetch(url, {
  credentials: 'include'
})

Use async-await with Fetch API

Since Fetch is a promise-based API, we can go one step further and use the latest ES2017 async/await syntax to make our code even simpler and synchronous-looking:

const fetchUsers = async () => {
  try {
    const res = await fetch('https://reqres.in/api/users')
    if (!res.ok) {
      throw new Error(res.status)
    }
    const data = await res.json()
    console.log(data)
  } catch (error) {
    console.log(error)
  }
}

fetchUsers()

Conclusion

That’s all folks for introduction to JavaScript Fetch API. It is a significant improvement over XMLHttpRequest with a simple, elegant, and easy-to-use interface. Fetch works great for fetching network resources (even across the network inside the service workers).

The Fetch API is supported by all modern browsers, so there is no need to use any polyfill unless you want to support IE.

Read next: How to make HTTP requests using XHR in JavaScript.

✌️ Like this article? Follow me on
Twitter
and LinkedIn.
You can also subscribe to
RSS Feed.

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

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

  • Ffr 03277 00 ошибка на ман тга
  • Ffr 03277 00 ошибка man tga
  • Ffr 03263 08 ошибка ман тга
  • Ffr 03263 08 ошибка man tga
  • Fetch event respondwith received an error returned response is null ошибка

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

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