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