├── README.md ├── fetch-polyfill.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | A polyfill for React Native's `whatwg-fetch`'s mirror. 2 | 3 | ### The polyfill 4 | 5 | This adds support for `timeout` as one of the `fetch` options. 6 | 7 | ```js 8 | import fetch from 'react-native-fetch-polyfill'; 9 | 10 | fetch(url, {timeout: 30 * 1000}) 11 | .then(response => { 12 | // a successful response 13 | }) 14 | .catch(error => { 15 | // an error when the request fails, such as during a timeout 16 | }) 17 | ``` 18 | 19 | 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. 20 | 21 | 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. 22 | 23 | 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). 24 | 25 | 26 | ### What is fetch? 27 | 28 | 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). 29 | 30 | ### Why a polyfill? 31 | 32 | Fetch has two challenges: 33 | - It cannot be externally aborted (https://github.com/whatwg/fetch/issues/27 and https://github.com/whatwg/fetch/issues/447) 34 | - 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) 35 | 36 | 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. 37 | 38 | ### How is the polyfill maintained? 39 | 40 | The polyfill picks out specific pieces of [whatwg/fetch](https://github.com/whatwg/fetch) required to apply the patch. 41 | 42 | The tagged version of the polyfill corresponds to the version of `fetch` that it patches. 43 | 44 | When new versions of `fetch` are released, the polyfill will be updated and tagged. 45 | -------------------------------------------------------------------------------- /fetch-polyfill.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var self = this || global; 4 | 5 | // Polyfill from https://github.com/github/fetch/blob/v1.1.1/fetch.js#L8-L21 6 | var support = { 7 | searchParams: 'URLSearchParams' in self, 8 | iterable: 'Symbol' in self && 'iterator' in Symbol, 9 | blob: 'FileReader' in self && 'Blob' in self && (function() { 10 | try { 11 | new Blob() 12 | return true 13 | } catch(e) { 14 | return false 15 | } 16 | })(), 17 | formData: 'FormData' in self, 18 | arrayBuffer: 'ArrayBuffer' in self 19 | } 20 | 21 | // Polyfill from https://github.com/github/fetch/blob/v1.1.1/fetch.js#L364-L375 22 | function parseHeaders(rawHeaders) { 23 | var headers = new Headers() 24 | rawHeaders.split(/\r?\n/).forEach(function(line) { 25 | var parts = line.split(':') 26 | var key = parts.shift().trim() 27 | if (key) { 28 | var value = parts.join(':').trim() 29 | headers.append(key, value) 30 | } 31 | }); 32 | 33 | return headers; 34 | } 35 | 36 | // Polyfill from https://github.com/github/fetch/blob/v1.1.1/fetch.js#L424-L464 37 | export default function fetchPolyfill (input, init) { 38 | return new Promise(function(resolve, reject) { 39 | var request = new Request(input, init) 40 | var xhr = new XMLHttpRequest() 41 | 42 | /* @patch: timeout */ 43 | if (init && init.timeout) { 44 | xhr.timeout = init.timeout; 45 | } 46 | /* @endpatch */ 47 | 48 | xhr.onload = function() { 49 | var options = { 50 | status: xhr.status, 51 | statusText: xhr.statusText, 52 | headers: parseHeaders(xhr.getAllResponseHeaders() || '') 53 | } 54 | options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') 55 | var body = 'response' in xhr ? xhr.response : xhr.responseText 56 | resolve(new Response(body, options)) 57 | } 58 | 59 | xhr.onerror = function() { 60 | reject(new TypeError('Network request failed')) 61 | } 62 | 63 | xhr.ontimeout = function() { 64 | reject(new TypeError('Network request failed')) 65 | } 66 | 67 | xhr.open(request.method, request.url, true) 68 | 69 | if (request.credentials === 'include') { 70 | xhr.withCredentials = true 71 | } 72 | 73 | if ('responseType' in xhr && support.blob) { 74 | xhr.responseType = 'blob' 75 | } 76 | 77 | request.headers.forEach(function(value, name) { 78 | xhr.setRequestHeader(name, value) 79 | }) 80 | 81 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-fetch-polyfill", 3 | "version": "1.1.3", 4 | "description": "A polyfill for React Native's fetch client", 5 | "main": "fetch-polyfill.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/robinpowered/react-native-fetch-polyfill.git" 12 | }, 13 | "keywords": [ 14 | "ReactNative", 15 | "fetch", 16 | "timeout" 17 | ], 18 | "peerDependencies": { 19 | "react-native": ">=0.27" 20 | }, 21 | "contributors": [{ 22 | "name": "Atticus White", 23 | "email": "atticus@robinpowered.com" 24 | }], 25 | "license": "Apache-2.0", 26 | "bugs": { 27 | "url": "https://github.com/robinpowered/react-native-fetch-polyfill/issues" 28 | }, 29 | "homepage": "https://github.com/robinpowered/react-native-fetch-polyfill#readme" 30 | } 31 | --------------------------------------------------------------------------------