-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from robinpowered/polyfill
Fetch timeout polyfill
- Loading branch information
Showing
3 changed files
with
133 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
A polyfill for React Native's `whatwg-fetch`'s mirror. | ||
|
||
### The polyfill | ||
|
||
This adds support for `timeout` as one of the `fetch` options. | ||
|
||
```js | ||
import fetch from 'react-native-fetch-polyfill'; | ||
|
||
fetch(url, {timeout: 30 * 1000}) | ||
.then(response => { | ||
// a successful response | ||
}) | ||
.catch(error => { | ||
// an error when the request fails, such as during a timeout | ||
}) | ||
``` | ||
|
||
React Native's `XMLHttpRequest` interface [exposes a timeout property sent to the `RCTNetworking` module](https://github.com/facebook/react-native/blob/v0.42.1/Libraries/Network/XMLHttpRequest.js#L500), as well as an [abort method](https://github.com/facebook/react-native/blob/v0.42.1/Libraries/Network/XMLHttpRequest.js#L505-L520). `fetch` does not expose access to this by default, this polyfill allows specifying a `timeout` within the options. | ||
|
||
This value [attached to `NSMutableURLRequest`](https://github.com/facebook/react-native/blob/v0.42.1/Libraries/Network/RCTNetworking.mm#L232), where the native networking layer will enforce the timeout rule. | ||
|
||
The result of the timeout being reached will result in a promise [rejected with a `TypeError('Network rqeuest failed')](https://github.com/github/fetch/blob/v1.1.1/fetch.js#L445). | ||
|
||
|
||
### What is fetch? | ||
|
||
Fetch is a networking abstraction above [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). It reflects the [WHATWG fetch specification](https://fetch.spec.whatwg.org/) and can be found in [whatwg/fetch](https://github.com/whatwg/fetch). It is the networking library [used in React Native](https://facebook.github.io/react-native/docs/network.html#using-fetch). | ||
|
||
### Why a polyfill? | ||
|
||
Fetch has two challenges: | ||
- It cannot be externally aborted (https://github.com/whatwg/fetch/issues/27 and https://github.com/whatwg/fetch/issues/447) | ||
- It does not support `timeout`(https://github.com/facebook/react-native/issues/2394, https://github.com/facebook/react-native/issues/2556, https://github.com/whatwg/fetch/issues/20, https://github.com/github/fetch/issues/175) | ||
|
||
Why are these not supported? As a `fetch` maintainer points out in https://github.com/github/fetch/pull/68#issuecomment-70103306, the spec does not describe a standard for this behavior. | ||
|
||
### How is the polyfill maintained? | ||
|
||
The polyfill picks out specific pieces of [whatwg/fetch](https://github.com/whatwg/fetch) required to apply the patch. | ||
|
||
The tagged version of the polyfill corresponds to the version of `fetch` that it patches. | ||
|
||
When new versions of `fetch` are released, the polyfill will be updated and tagged. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
'use strict'; | ||
|
||
// Polyfill from https://github.com/github/fetch/blob/v1.1.1/fetch.js#L8-L21 | ||
var support = { | ||
searchParams: 'URLSearchParams' in self, | ||
iterable: 'Symbol' in self && 'iterator' in Symbol, | ||
blob: 'FileReader' in self && 'Blob' in self && (function() { | ||
try { | ||
new Blob() | ||
return true | ||
} catch(e) { | ||
return false | ||
} | ||
})(), | ||
formData: 'FormData' in self, | ||
arrayBuffer: 'ArrayBuffer' in self | ||
} | ||
|
||
// Polyfill from https://github.com/github/fetch/blob/v1.1.1/fetch.js#L364-L375 | ||
function parseHeaders(rawHeaders) { | ||
var headers = new Headers() | ||
rawHeaders.split(/\r?\n/).forEach(function(line) { | ||
var parts = line.split(':') | ||
var key = parts.shift().trim() | ||
if (key) { | ||
var value = parts.join(':').trim() | ||
headers.append(key, value) | ||
} | ||
}); | ||
|
||
return headers; | ||
} | ||
|
||
// Polyfill from https://github.com/github/fetch/blob/v1.1.1/fetch.js#L424-L464 | ||
export default function fetchPolyfill (input, init) { | ||
return new Promise(function(resolve, reject) { | ||
var request = new Request(input, init) | ||
var xhr = new XMLHttpRequest() | ||
|
||
/* @patch: timeout */ | ||
if (init.timeout) { | ||
xhr.timeout = init.timeout; | ||
} | ||
/* @endpatch */ | ||
|
||
xhr.onload = function() { | ||
var options = { | ||
status: xhr.status, | ||
statusText: xhr.statusText, | ||
headers: parseHeaders(xhr.getAllResponseHeaders() || '') | ||
} | ||
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') | ||
var body = 'response' in xhr ? xhr.response : xhr.responseText | ||
resolve(new Response(body, options)) | ||
} | ||
|
||
xhr.onerror = function() { | ||
reject(new TypeError('Network request failed')) | ||
} | ||
|
||
xhr.ontimeout = function() { | ||
reject(new TypeError('Network request failed')) | ||
} | ||
|
||
xhr.open(request.method, request.url, true) | ||
|
||
if (request.credentials === 'include') { | ||
xhr.withCredentials = true | ||
} | ||
|
||
if ('responseType' in xhr && support.blob) { | ||
xhr.responseType = 'blob' | ||
} | ||
|
||
request.headers.forEach(function(value, name) { | ||
xhr.setRequestHeader(name, value) | ||
}) | ||
|
||
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters