Hi, Guys
I currently ran into a issue that when using fetch to call remote data, I can't handle the timeout situation, if my not return successfully, it will wait forever.
I have google for awhile and I didn't find any good practice to handle, npm fetch doc says it has a timeout keyword in options object, but seems it didn't work at all.
how do you guys handle this situation?
Check out this discussion here: https://github.com/github/fetch/issues/175 - there is a solution implemented there that uses promises and seems nice.
Lots of discussion going on about the fetch specification here:
https://github.com/whatwg/fetch/issues/27
https://github.com/whatwg/fetch/issues/20
Another attempt at wrapping fetch to add this: https://github.com/whatwg/fetch/issues/20#issuecomment-135426968
I really don't agree the above solutions. If fetch wait forever and you use setTimeout to reject the wrapper promise, then the fetch will hold the reference of the resolve and reject forever.
It may seem not so huge of a waster but if the code try to use fetch hundreds of times and timeout happens a lot, then the memory leak will be a problem.
I agree with @narychen, its a waste of memory. A big discussion is going on over here whatwg/fetch#20 and seems not have any solution. Here's what I did:
I now have a timeout of 10 seconds on my fetch requests.
You can find my modified whatwg fetch.js file here: https://gist.github.com/kyle-ilantzis/e4521e6194f6c019b14ac91291feb348
You can find the setTimeout I added here: https://gist.github.com/kyle-ilantzis/e4521e6194f6c019b14ac91291feb348#file-whatwg-fetch-js-L440
You can modify 10 seconds to what ever you want here: https://gist.github.com/kyle-ilantzis/e4521e6194f6c019b14ac91291feb348#file-whatwg-fetch-js-L8
I any one is interested, here is the self.fetch.hack = true:
https://gist.github.com/kyle-ilantzis/e4521e6194f6c019b14ac91291feb348#file-whatwg-fetch-js-L4
https://gist.github.com/kyle-ilantzis/e4521e6194f6c019b14ac91291feb348#file-whatwg-fetch-js-L466
Try https://github.com/benestudio/fetch/tree/feature/timeout
npm install whatwg-fetch-timeout --save;return fetch('/path', {timeout: 500}).then(function() {
// successful fetch
}).catch(function(error) {
// network request failed / timeout
})
This fork supports timout just like node-fetch.
This way you can have timeouts runtime and bundle time also. Say goodbye to memory leaks.
Hope it could be merged some sunny day.
Any idea on how to use my fetch fork without forking and patching RN itself?
@adambene you want your polyfill to overwrite react-native's polyfill.
The first lines of fetch.js look like
if (self.fetch) {
return
}
Which will be true because the fetch function already exists.
I suggest doing
global.fetch = null;
require('whatwg-fetch-timeout')
(I am not sure but i think it would be in global)
Or changing the if to check for your fetch function such as if (fetch.hasTimeout) { return }
@kyle-ilantzis whatwg-fetch is already imported before I could do anything: https://github.com/facebook/react-native/blob/292cc82d0ebc437a6f1cdd2e972b3917b7ee05a4/Libraries/Network/fetch.js
@adambene yes whatwg-fetch is already imported. You want your whatwg-fetch-timeout to take precedence. I imagine you are importing yours? The problem is in your fetch.js file at the top of the file there is
if (self.fetch) {
return
}
@kyle-ilantzis I feel that whatwg-fetch-timeout should only be an extension to whatwg-fetch, so removing the fetch implementation check seems hacky to me.
I've tried npm-shrinkwrap, but does not allow different package names only versions to be overridden.
I am searching for a really maintainable and universal solution, because all my future projects are going to rely on fetch timeout to achieve robustness. That's why forking RN nor hacking whatwg-fetch is not a good option.
(Edit: of course the most welcome would be a warm merge for the timeout feature.)
It seems that there are 3 choices to override fetch in RN. And an extra.
Fork RN and rename the whatwg-fetch dependency to whatwg-fetch-timeout.
Well, it's a maintenance hell.
whatwg-fetch-timeout in all filesImport whatwg-fetch-timeout in all relevant .js files overriding fetch provided by RN.
Explicit and clean, just don't forget to import everywhere.
Override global.fetch the hacky way: see also https://github.com/facebook/react-native/blob/dba133a29194e300e9a2e9e6753f9d4e3a13c194/Libraries/Core/InitializeCore.js
Let pending requests in the air. Pretend if the fetch was timed out without aborting it (https://github.com/building5/promise-timeout) and give you users the option to retry. Watch out for patch and post especially when mobile network is bad. Chaos may happen.
Use whatwg-fetch-timeout as an alternative
npm install whatwg-fetch-timeout --save
Create a new timeoutFetch function by clearing global.fetch before loading the module.
var timeoutFetch = null;
(()=>{
var backup = global.fetch;
global.fetch = null;
require('whatwg-fetch-timeout');
timeoutFetch = global.fetch;
global.fetch = backup;
})();
Then replace your calls for fetch(...) with timeoutFetch(...). Alternatively, you can not restore global.fetch, but that could have some unintended consequences.
Our team is currently using robinpowered/react-native-fetch-polyfill.
It essentially provides timeout to the XMLHttpRequest instance, which is already equipped to natively support request timeouts. It's just a problem where fetch does not expose this. The polyfill opens this up.
It currently avoids writing to the global scope, but I suppose we could make that an option
import fetch from 'react-native-fetch-polyfill';
fetch(path, {timeout: 30 * 1000})
@adambene For me, importing 'whatwg-fetch-timeout' in all files did not work. I tried the idea by @clshortfuse. The following code did not work:
const backup = global.fetch;
global.fetch = null;
require('whatwg-fetch-timeout');
const timeoutFetch = global.fetch;
global.fetch = backup;
I used timeoutFetch('login', {timeout: 10000, method: 'PUT'});. The call timed out in 90 seconds.
@adambene Also, why should whatwg-fetch-timeout be imported in all files? Since, we are patching, should it not be done in the main file, say index.android.js?
@ajwhite I published react-native-fetch-timeout. It seems to set the timeout on xhr. But ontimeout callback is not triggered after the timeout has elapsed.
@vijayst because RN hacks fetch into the global. See: https://github.com/facebook/react-native/blob/dba133a29194e300e9a2e9e6753f9d4e3a13c194/Libraries/Core/InitializeCore.js#L182
So it is not so straightforward to override that properly.
For reference I have this question on StackOverflow: http://stackoverflow.com/questions/42495366/react-native-custom-fetch-polyfill
@adambene you should have luck with react-native-fetch-polyfill with the following:
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
})
This doesn't replace the global.fetch assignment, but it does let you import the polyfilled version with the timeout; which you could give the import a different name to avoid conflicting with the global.fetch name.
@vijayst I have not attempted to replace global.fetch with the polyfill, so I can't speak to that. If global.fetch is defined with writable: false in the Object.defineProperty call, then it will not be replaceable.
I am using Promise.race to work around this. Though the fetch promise won't be aborted.
const delay = (timeout, error) => new Promise((resolve, reject) => setTimeout(() => reject(error), timeout))
const result = await Promise.race([fetchPromise, delay(30 * 1000, 'Timeout Error')]
@ajwhite yeah, this package does the same: https://www.npmjs.com/package/whatwg-fetch-timeout
repo/branch: https://github.com/benestudio/fetch/tree/feature/timeout
@ajwhite solution work for me! (React Native 0.52 on Android)
Most helpful comment
@adambene you should have luck with react-native-fetch-polyfill with the following:
This doesn't replace the
global.fetchassignment, but it does let you import the polyfilled version with the timeout; which you could give the import a different name to avoid conflicting with theglobal.fetchname.@vijayst I have not attempted to replace
global.fetchwith the polyfill, so I can't speak to that. Ifglobal.fetchis defined withwritable: falsein theObject.definePropertycall, then it will not be replaceable.