1 | # window.fetch polyfill
|
2 |
|
3 | The `fetch()` function is a Promise-based mechanism for programmatically making
|
4 | web requests in the browser. This project is a polyfill that implements a subset
|
5 | of the standard [Fetch specification][], enough to make `fetch` a viable
|
6 | replacement for most uses of XMLHttpRequest in traditional web applications.
|
7 |
|
8 | ## Table of Contents
|
9 |
|
10 | * [Read this first](#read-this-first)
|
11 | * [Installation](#installation)
|
12 | * [Usage](#usage)
|
13 | * [Importing](#importing)
|
14 | * [HTML](#html)
|
15 | * [JSON](#json)
|
16 | * [Response metadata](#response-metadata)
|
17 | * [Post form](#post-form)
|
18 | * [Post JSON](#post-json)
|
19 | * [File upload](#file-upload)
|
20 | * [Caveats](#caveats)
|
21 | * [Handling HTTP error statuses](#handling-http-error-statuses)
|
22 | * [Sending cookies](#sending-cookies)
|
23 | * [Receiving cookies](#receiving-cookies)
|
24 | * [Redirect modes](#redirect-modes)
|
25 | * [Obtaining the Response URL](#obtaining-the-response-url)
|
26 | * [Aborting requests](#aborting-requests)
|
27 | * [Browser Support](#browser-support)
|
28 |
|
29 | ## Read this first
|
30 |
|
31 | * If you believe you found a bug with how `fetch` behaves in your browser,
|
32 | please **don't open an issue in this repository** unless you are testing in
|
33 | an old version of a browser that doesn't support `window.fetch` natively.
|
34 | This project is a _polyfill_, and since all modern browsers now implement the
|
35 | `fetch` function natively, **no code from this project** actually takes any
|
36 | effect there. See [Browser support](#browser-support) for detailed
|
37 | information.
|
38 |
|
39 | * If you have trouble **making a request to another domain** (a different
|
40 | subdomain or port number also constitutes another domain), please familiarize
|
41 | yourself with all the intricacies and limitations of [CORS][] requests.
|
42 | Because CORS requires participation of the server by implementing specific
|
43 | HTTP response headers, it is often nontrivial to set up or debug. CORS is
|
44 | exclusively handled by the browser's internal mechanisms which this polyfill
|
45 | cannot influence.
|
46 |
|
47 | * This project **doesn't work under Node.js environments**. It's meant for web
|
48 | browsers only. You should ensure that your application doesn't try to package
|
49 | and run this on the server.
|
50 |
|
51 | * If you have an idea for a new feature of `fetch`, **submit your feature
|
52 | requests** to the [specification's repository](https://github.com/whatwg/fetch/issues).
|
53 | We only add features and APIs that are part of the [Fetch specification][].
|
54 |
|
55 | ## Installation
|
56 |
|
57 | ```
|
58 | npm install whatwg-fetch --save
|
59 | ```
|
60 |
|
61 | As an alternative to using npm, you can obtain `fetch.umd.js` from the
|
62 | [Releases][] section. The UMD distribution is compatible with AMD and CommonJS
|
63 | module loaders, as well as loading directly into a page via `<script>` tag.
|
64 |
|
65 | You will also need a Promise polyfill for [older browsers](http://caniuse.com/#feat=promises).
|
66 | We recommend [taylorhakes/promise-polyfill](https://github.com/taylorhakes/promise-polyfill)
|
67 | for its small size and Promises/A+ compatibility.
|
68 |
|
69 | ## Usage
|
70 |
|
71 | For a more comprehensive API reference that this polyfill supports, refer to
|
72 | https://github.github.io/fetch/.
|
73 |
|
74 | ### Importing
|
75 |
|
76 | Importing will automatically polyfill `window.fetch` and related APIs:
|
77 |
|
78 | ```javascript
|
79 | import 'whatwg-fetch'
|
80 |
|
81 | window.fetch(...)
|
82 | ```
|
83 |
|
84 | If for some reason you need to access the polyfill implementation, it is
|
85 | available via exports:
|
86 |
|
87 | ```javascript
|
88 | import {fetch as fetchPolyfill} from 'whatwg-fetch'
|
89 |
|
90 | window.fetch(...) // use native browser version
|
91 | fetchPolyfill(...) // use polyfill implementation
|
92 | ```
|
93 |
|
94 | This approach can be used to, for example, use [abort
|
95 | functionality](#aborting-requests) in browsers that implement a native but
|
96 | outdated version of fetch that doesn't support aborting.
|
97 |
|
98 | For use with webpack, add this package in the `entry` configuration option
|
99 | before your application entry point:
|
100 |
|
101 | ```javascript
|
102 | entry: ['whatwg-fetch', ...]
|
103 | ```
|
104 |
|
105 | ### HTML
|
106 |
|
107 | ```javascript
|
108 | fetch('/users.html')
|
109 | .then(function(response) {
|
110 | return response.text()
|
111 | }).then(function(body) {
|
112 | document.body.innerHTML = body
|
113 | })
|
114 | ```
|
115 |
|
116 | ### JSON
|
117 |
|
118 | ```javascript
|
119 | fetch('/users.json')
|
120 | .then(function(response) {
|
121 | return response.json()
|
122 | }).then(function(json) {
|
123 | console.log('parsed json', json)
|
124 | }).catch(function(ex) {
|
125 | console.log('parsing failed', ex)
|
126 | })
|
127 | ```
|
128 |
|
129 | ### Response metadata
|
130 |
|
131 | ```javascript
|
132 | fetch('/users.json').then(function(response) {
|
133 | console.log(response.headers.get('Content-Type'))
|
134 | console.log(response.headers.get('Date'))
|
135 | console.log(response.status)
|
136 | console.log(response.statusText)
|
137 | })
|
138 | ```
|
139 |
|
140 | ### Post form
|
141 |
|
142 | ```javascript
|
143 | var form = document.querySelector('form')
|
144 |
|
145 | fetch('/users', {
|
146 | method: 'POST',
|
147 | body: new FormData(form)
|
148 | })
|
149 | ```
|
150 |
|
151 | ### Post JSON
|
152 |
|
153 | ```javascript
|
154 | fetch('/users', {
|
155 | method: 'POST',
|
156 | headers: {
|
157 | 'Content-Type': 'application/json'
|
158 | },
|
159 | body: JSON.stringify({
|
160 | name: 'Hubot',
|
161 | login: 'hubot',
|
162 | })
|
163 | })
|
164 | ```
|
165 |
|
166 | ### File upload
|
167 |
|
168 | ```javascript
|
169 | var input = document.querySelector('input[type="file"]')
|
170 |
|
171 | var data = new FormData()
|
172 | data.append('file', input.files[0])
|
173 | data.append('user', 'hubot')
|
174 |
|
175 | fetch('/avatars', {
|
176 | method: 'POST',
|
177 | body: data
|
178 | })
|
179 | ```
|
180 |
|
181 | ### Caveats
|
182 |
|
183 | * The Promise returned from `fetch()` **won't reject on HTTP error status**
|
184 | even if the response is an HTTP 404 or 500. Instead, it will resolve normally,
|
185 | and it will only reject on network failure or if anything prevented the
|
186 | request from completing.
|
187 |
|
188 | * For maximum browser compatibility when it comes to sending & receiving
|
189 | cookies, always supply the `credentials: 'same-origin'` option instead of
|
190 | relying on the default. See [Sending cookies](#sending-cookies).
|
191 |
|
192 | * Not all Fetch standard options are supported in this polyfill. For instance,
|
193 | [`redirect`](#redirect-modes) and
|
194 | [`cache`](https://github.github.io/fetch/#caveats) directives are ignored.
|
195 |
|
196 | #### Handling HTTP error statuses
|
197 |
|
198 | To have `fetch` Promise reject on HTTP error statuses, i.e. on any non-2xx
|
199 | status, define a custom response handler:
|
200 |
|
201 | ```javascript
|
202 | function checkStatus(response) {
|
203 | if (response.status >= 200 && response.status < 300) {
|
204 | return response
|
205 | } else {
|
206 | var error = new Error(response.statusText)
|
207 | error.response = response
|
208 | throw error
|
209 | }
|
210 | }
|
211 |
|
212 | function parseJSON(response) {
|
213 | return response.json()
|
214 | }
|
215 |
|
216 | fetch('/users')
|
217 | .then(checkStatus)
|
218 | .then(parseJSON)
|
219 | .then(function(data) {
|
220 | console.log('request succeeded with JSON response', data)
|
221 | }).catch(function(error) {
|
222 | console.log('request failed', error)
|
223 | })
|
224 | ```
|
225 |
|
226 | #### Sending cookies
|
227 |
|
228 | For [CORS][] requests, use `credentials: 'include'` to allow sending credentials
|
229 | to other domains:
|
230 |
|
231 | ```javascript
|
232 | fetch('https://example.com:1234/users', {
|
233 | credentials: 'include'
|
234 | })
|
235 | ```
|
236 |
|
237 | The default value for `credentials` is "same-origin".
|
238 |
|
239 | The default for `credentials` wasn't always the same, though. The following
|
240 | versions of browsers implemented an older version of the fetch specification
|
241 | where the default was "omit":
|
242 |
|
243 | * Firefox 39-60
|
244 | * Chrome 42-67
|
245 | * Safari 10.1-11.1.2
|
246 |
|
247 | If you target these browsers, it's advisable to always specify `credentials:
|
248 | 'same-origin'` explicitly with all fetch requests instead of relying on the
|
249 | default:
|
250 |
|
251 | ```javascript
|
252 | fetch('/users', {
|
253 | credentials: 'same-origin'
|
254 | })
|
255 | ```
|
256 |
|
257 | Note: due to [limitations of
|
258 | XMLHttpRequest](https://github.com/github/fetch/pull/56#issuecomment-68835992),
|
259 | using `credentials: 'omit'` is not respected for same domains in browsers where
|
260 | this polyfill is active. Cookies will always be sent to same domains in older
|
261 | browsers.
|
262 |
|
263 | #### Receiving cookies
|
264 |
|
265 | As with XMLHttpRequest, the `Set-Cookie` response header returned from the
|
266 | server is a [forbidden header name][] and therefore can't be programmatically
|
267 | read with `response.headers.get()`. Instead, it's the browser's responsibility
|
268 | to handle new cookies being set (if applicable to the current URL). Unless they
|
269 | are HTTP-only, new cookies will be available through `document.cookie`.
|
270 |
|
271 | #### Redirect modes
|
272 |
|
273 | The Fetch specification defines these values for [the `redirect`
|
274 | option](https://fetch.spec.whatwg.org/#concept-request-redirect-mode): "follow"
|
275 | (the default), "error", and "manual".
|
276 |
|
277 | Due to limitations of XMLHttpRequest, only the "follow" mode is available in
|
278 | browsers where this polyfill is active.
|
279 |
|
280 | #### Obtaining the Response URL
|
281 |
|
282 | Due to limitations of XMLHttpRequest, the `response.url` value might not be
|
283 | reliable after HTTP redirects on older browsers.
|
284 |
|
285 | The solution is to configure the server to set the response HTTP header
|
286 | `X-Request-URL` to the current URL after any redirect that might have happened.
|
287 | It should be safe to set it unconditionally.
|
288 |
|
289 | ``` ruby
|
290 | # Ruby on Rails controller example
|
291 | response.headers['X-Request-URL'] = request.url
|
292 | ```
|
293 |
|
294 | This server workaround is necessary if you need reliable `response.url` in
|
295 | Firefox < 32, Chrome < 37, Safari, or IE.
|
296 |
|
297 | #### Aborting requests
|
298 |
|
299 | This polyfill supports
|
300 | [the abortable fetch API](https://developers.google.com/web/updates/2017/09/abortable-fetch).
|
301 | However, aborting a fetch requires use of two additional DOM APIs:
|
302 | [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and
|
303 | [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal).
|
304 | Typically, browsers that do not support fetch will also not support
|
305 | AbortController or AbortSignal. Consequently, you will need to include
|
306 | [an additional polyfill](https://github.com/mo/abortcontroller-polyfill#readme)
|
307 | for these APIs to abort fetches:
|
308 |
|
309 | ```js
|
310 | import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
311 | import {fetch} from 'whatwg-fetch'
|
312 |
|
313 | // use native browser implementation if it supports aborting
|
314 | const abortableFetch = ('signal' in new Request('')) ? window.fetch : fetch
|
315 |
|
316 | const controller = new AbortController()
|
317 |
|
318 | abortableFetch('/avatars', {
|
319 | signal: controller.signal
|
320 | }).catch(function(ex) {
|
321 | if (ex.name === 'AbortError') {
|
322 | console.log('request aborted')
|
323 | }
|
324 | })
|
325 |
|
326 | // some time later...
|
327 | controller.abort()
|
328 | ```
|
329 |
|
330 | ## Browser Support
|
331 |
|
332 | - Chrome
|
333 | - Firefox
|
334 | - Safari 6.1+
|
335 | - Internet Explorer 10+
|
336 |
|
337 | Note: modern browsers such as Chrome, Firefox, Microsoft Edge, and Safari contain native
|
338 | implementations of `window.fetch`, therefore the code from this polyfill doesn't
|
339 | have any effect on those browsers. If you believe you've encountered an error
|
340 | with how `window.fetch` is implemented in any of these browsers, you should file
|
341 | an issue with that browser vendor instead of this project.
|
342 |
|
343 |
|
344 | [fetch specification]: https://fetch.spec.whatwg.org
|
345 | [cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
346 | "Cross-origin resource sharing"
|
347 | [csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
|
348 | "Cross-site request forgery"
|
349 | [forbidden header name]: https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
|
350 | [releases]: https://github.com/github/fetch/releases
|
351 |
|
\ | No newline at end of file |