You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
11 KiB
354 lines
11 KiB
# window.fetch polyfill |
|
|
|
The `fetch()` function is a Promise-based mechanism for programmatically making |
|
web requests in the browser. This project is a polyfill that implements a subset |
|
of the standard [Fetch specification][], enough to make `fetch` a viable |
|
replacement for most uses of XMLHttpRequest in traditional web applications. |
|
|
|
## Table of Contents |
|
|
|
* [Read this first](#read-this-first) |
|
* [Installation](#installation) |
|
* [Usage](#usage) |
|
* [Importing](#importing) |
|
* [HTML](#html) |
|
* [JSON](#json) |
|
* [Response metadata](#response-metadata) |
|
* [Post form](#post-form) |
|
* [Post JSON](#post-json) |
|
* [File upload](#file-upload) |
|
* [Caveats](#caveats) |
|
* [Handling HTTP error statuses](#handling-http-error-statuses) |
|
* [Sending cookies](#sending-cookies) |
|
* [Receiving cookies](#receiving-cookies) |
|
* [Redirect modes](#redirect-modes) |
|
* [Obtaining the Response URL](#obtaining-the-response-url) |
|
* [Aborting requests](#aborting-requests) |
|
* [Browser Support](#browser-support) |
|
|
|
## Read this first |
|
|
|
* If you believe you found a bug with how `fetch` behaves in your browser, |
|
please **don't open an issue in this repository** unless you are testing in |
|
an old version of a browser that doesn't support `window.fetch` natively. |
|
Make sure you read this _entire_ readme, especially the [Caveats](#caveats) |
|
section, as there's probably a known work-around for an issue you've found. |
|
This project is a _polyfill_, and since all modern browsers now implement the |
|
`fetch` function natively, **no code from this project** actually takes any |
|
effect there. See [Browser support](#browser-support) for detailed |
|
information. |
|
|
|
* If you have trouble **making a request to another domain** (a different |
|
subdomain or port number also constitutes another domain), please familiarize |
|
yourself with all the intricacies and limitations of [CORS][] requests. |
|
Because CORS requires participation of the server by implementing specific |
|
HTTP response headers, it is often nontrivial to set up or debug. CORS is |
|
exclusively handled by the browser's internal mechanisms which this polyfill |
|
cannot influence. |
|
|
|
* This project **doesn't work under Node.js environments**. It's meant for web |
|
browsers only. You should ensure that your application doesn't try to package |
|
and run this on the server. |
|
|
|
* If you have an idea for a new feature of `fetch`, **submit your feature |
|
requests** to the [specification's repository](https://github.com/whatwg/fetch/issues). |
|
We only add features and APIs that are part of the [Fetch specification][]. |
|
|
|
## Installation |
|
|
|
``` |
|
npm install whatwg-fetch --save |
|
``` |
|
|
|
As an alternative to using npm, you can obtain `fetch.umd.js` from the |
|
[Releases][] section. The UMD distribution is compatible with AMD and CommonJS |
|
module loaders, as well as loading directly into a page via `<script>` tag. |
|
|
|
You will also need a Promise polyfill for [older browsers](http://caniuse.com/#feat=promises). |
|
We recommend [taylorhakes/promise-polyfill](https://github.com/taylorhakes/promise-polyfill) |
|
for its small size and Promises/A+ compatibility. |
|
|
|
## Usage |
|
|
|
For a more comprehensive API reference that this polyfill supports, refer to |
|
https://github.github.io/fetch/. |
|
|
|
### Importing |
|
|
|
Importing will automatically polyfill `window.fetch` and related APIs: |
|
|
|
```javascript |
|
import 'whatwg-fetch' |
|
|
|
window.fetch(...) |
|
``` |
|
|
|
If for some reason you need to access the polyfill implementation, it is |
|
available via exports: |
|
|
|
```javascript |
|
import {fetch as fetchPolyfill} from 'whatwg-fetch' |
|
|
|
window.fetch(...) // use native browser version |
|
fetchPolyfill(...) // use polyfill implementation |
|
``` |
|
|
|
This approach can be used to, for example, use [abort |
|
functionality](#aborting-requests) in browsers that implement a native but |
|
outdated version of fetch that doesn't support aborting. |
|
|
|
For use with webpack, add this package in the `entry` configuration option |
|
before your application entry point: |
|
|
|
```javascript |
|
entry: ['whatwg-fetch', ...] |
|
``` |
|
|
|
### HTML |
|
|
|
```javascript |
|
fetch('/users.html') |
|
.then(function(response) { |
|
return response.text() |
|
}).then(function(body) { |
|
document.body.innerHTML = body |
|
}) |
|
``` |
|
|
|
### JSON |
|
|
|
```javascript |
|
fetch('/users.json') |
|
.then(function(response) { |
|
return response.json() |
|
}).then(function(json) { |
|
console.log('parsed json', json) |
|
}).catch(function(ex) { |
|
console.log('parsing failed', ex) |
|
}) |
|
``` |
|
|
|
### Response metadata |
|
|
|
```javascript |
|
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) |
|
}) |
|
``` |
|
|
|
### Post form |
|
|
|
```javascript |
|
var form = document.querySelector('form') |
|
|
|
fetch('/users', { |
|
method: 'POST', |
|
body: new FormData(form) |
|
}) |
|
``` |
|
|
|
### Post JSON |
|
|
|
```javascript |
|
fetch('/users', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
name: 'Hubot', |
|
login: 'hubot', |
|
}) |
|
}) |
|
``` |
|
|
|
### File upload |
|
|
|
```javascript |
|
var input = document.querySelector('input[type="file"]') |
|
|
|
var data = new FormData() |
|
data.append('file', input.files[0]) |
|
data.append('user', 'hubot') |
|
|
|
fetch('/avatars', { |
|
method: 'POST', |
|
body: data |
|
}) |
|
``` |
|
|
|
### Caveats |
|
|
|
* The Promise returned from `fetch()` **won't reject on HTTP error status** |
|
even if the response is an HTTP 404 or 500. Instead, it will resolve normally, |
|
and it will only reject on network failure or if anything prevented the |
|
request from completing. |
|
|
|
* For maximum browser compatibility when it comes to sending & receiving |
|
cookies, always supply the `credentials: 'same-origin'` option instead of |
|
relying on the default. See [Sending cookies](#sending-cookies). |
|
|
|
* Not all Fetch standard options are supported in this polyfill. For instance, |
|
[`redirect`](#redirect-modes) and |
|
[`cache`](https://github.github.io/fetch/#caveats) directives are ignored. |
|
|
|
* `keepalive` is not supported because it would involve making a synchronous XHR, which is something this project is not willing to do. See [issue #700](https://github.com/github/fetch/issues/700#issuecomment-484188326) for more information. |
|
|
|
#### Handling HTTP error statuses |
|
|
|
To have `fetch` Promise reject on HTTP error statuses, i.e. on any non-2xx |
|
status, define a custom response handler: |
|
|
|
```javascript |
|
function checkStatus(response) { |
|
if (response.status >= 200 && response.status < 300) { |
|
return response |
|
} else { |
|
var error = new Error(response.statusText) |
|
error.response = response |
|
throw error |
|
} |
|
} |
|
|
|
function parseJSON(response) { |
|
return response.json() |
|
} |
|
|
|
fetch('/users') |
|
.then(checkStatus) |
|
.then(parseJSON) |
|
.then(function(data) { |
|
console.log('request succeeded with JSON response', data) |
|
}).catch(function(error) { |
|
console.log('request failed', error) |
|
}) |
|
``` |
|
|
|
#### Sending cookies |
|
|
|
For [CORS][] requests, use `credentials: 'include'` to allow sending credentials |
|
to other domains: |
|
|
|
```javascript |
|
fetch('https://example.com:1234/users', { |
|
credentials: 'include' |
|
}) |
|
``` |
|
|
|
The default value for `credentials` is "same-origin". |
|
|
|
The default for `credentials` wasn't always the same, though. The following |
|
versions of browsers implemented an older version of the fetch specification |
|
where the default was "omit": |
|
|
|
* Firefox 39-60 |
|
* Chrome 42-67 |
|
* Safari 10.1-11.1.2 |
|
|
|
If you target these browsers, it's advisable to always specify `credentials: |
|
'same-origin'` explicitly with all fetch requests instead of relying on the |
|
default: |
|
|
|
```javascript |
|
fetch('/users', { |
|
credentials: 'same-origin' |
|
}) |
|
``` |
|
|
|
Note: due to [limitations of |
|
XMLHttpRequest](https://github.com/github/fetch/pull/56#issuecomment-68835992), |
|
using `credentials: 'omit'` is not respected for same domains in browsers where |
|
this polyfill is active. Cookies will always be sent to same domains in older |
|
browsers. |
|
|
|
#### Receiving cookies |
|
|
|
As with XMLHttpRequest, the `Set-Cookie` response header returned from the |
|
server is a [forbidden header name][] and therefore can't be programmatically |
|
read with `response.headers.get()`. Instead, it's the browser's responsibility |
|
to handle new cookies being set (if applicable to the current URL). Unless they |
|
are HTTP-only, new cookies will be available through `document.cookie`. |
|
|
|
#### Redirect modes |
|
|
|
The Fetch specification defines these values for [the `redirect` |
|
option](https://fetch.spec.whatwg.org/#concept-request-redirect-mode): "follow" |
|
(the default), "error", and "manual". |
|
|
|
Due to limitations of XMLHttpRequest, only the "follow" mode is available in |
|
browsers where this polyfill is active. |
|
|
|
#### Obtaining the Response URL |
|
|
|
Due to limitations of XMLHttpRequest, the `response.url` value might not be |
|
reliable after HTTP redirects on older browsers. |
|
|
|
The solution is to configure the server to set the response HTTP header |
|
`X-Request-URL` to the current URL after any redirect that might have happened. |
|
It should be safe to set it unconditionally. |
|
|
|
``` ruby |
|
# Ruby on Rails controller example |
|
response.headers['X-Request-URL'] = request.url |
|
``` |
|
|
|
This server workaround is necessary if you need reliable `response.url` in |
|
Firefox < 32, Chrome < 37, Safari, or IE. |
|
|
|
#### Aborting requests |
|
|
|
This polyfill supports |
|
[the abortable fetch API](https://developers.google.com/web/updates/2017/09/abortable-fetch). |
|
However, aborting a fetch requires use of two additional DOM APIs: |
|
[AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and |
|
[AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). |
|
Typically, browsers that do not support fetch will also not support |
|
AbortController or AbortSignal. Consequently, you will need to include |
|
[an additional polyfill](https://www.npmjs.com/package/yet-another-abortcontroller-polyfill) |
|
for these APIs to abort fetches: |
|
|
|
```js |
|
import 'yet-another-abortcontroller-polyfill' |
|
import {fetch} from 'whatwg-fetch' |
|
|
|
// use native browser implementation if it supports aborting |
|
const abortableFetch = ('signal' in new Request('')) ? window.fetch : fetch |
|
|
|
const controller = new AbortController() |
|
|
|
abortableFetch('/avatars', { |
|
signal: controller.signal |
|
}).catch(function(ex) { |
|
if (ex.name === 'AbortError') { |
|
console.log('request aborted') |
|
} |
|
}) |
|
|
|
// some time later... |
|
controller.abort() |
|
``` |
|
|
|
## Browser Support |
|
|
|
- Chrome |
|
- Firefox |
|
- Safari 6.1+ |
|
- Internet Explorer 10+ |
|
|
|
Note: modern browsers such as Chrome, Firefox, Microsoft Edge, and Safari contain native |
|
implementations of `window.fetch`, therefore the code from this polyfill doesn't |
|
have any effect on those browsers. If you believe you've encountered an error |
|
with how `window.fetch` is implemented in any of these browsers, you should file |
|
an issue with that browser vendor instead of this project. |
|
|
|
|
|
[fetch specification]: https://fetch.spec.whatwg.org |
|
[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS |
|
"Cross-origin resource sharing" |
|
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet |
|
"Cross-site request forgery" |
|
[forbidden header name]: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name |
|
[releases]: https://github.com/github/fetch/releases
|
|
|