rn: refactor loadScript
- use AbortController for setting the fetch timeout - use async / await syntax for clarify - set the default timeout to 5s (previously non-existent, aka 0) - add ability to load but not evaluate a script
This commit is contained in:
parent
1feff9709c
commit
35130f0736
|
@ -1,6 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { timeoutPromise } from './timeoutPromise';
|
/**
|
||||||
|
* Default timeout for loading scripts.
|
||||||
|
*/
|
||||||
|
const DEFAULT_TIMEOUT = 5000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a script from a specific URL. React Native cannot load a JS
|
* Loads a script from a specific URL. React Native cannot load a JS
|
||||||
|
@ -13,63 +16,49 @@ import { timeoutPromise } from './timeoutPromise';
|
||||||
* @param {number} [timeout] - The timeout in millisecnods after which the
|
* @param {number} [timeout] - The timeout in millisecnods after which the
|
||||||
* loading of the specified {@code url} is to be aborted/rejected (if not
|
* loading of the specified {@code url} is to be aborted/rejected (if not
|
||||||
* settled yet).
|
* settled yet).
|
||||||
|
* @param {boolean} skipEval - Wether we want to skip evaluating the loaded content or not.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export function loadScript(url: string, timeout: ?number): Promise<void> {
|
export async function loadScript(
|
||||||
return new Promise((resolve, reject) => {
|
url: string, timeout: number = DEFAULT_TIMEOUT, skipEval: boolean = false): Promise<any> {
|
||||||
// XXX The implementation of fetch on Android will throw an Exception on
|
// XXX The implementation of fetch on Android will throw an Exception on
|
||||||
// the Java side which will break the app if the URL is invalid (which
|
// the Java side which will break the app if the URL is invalid (which
|
||||||
// the implementation of fetch on Android calls 'unexpected url'). In
|
// the implementation of fetch on Android calls 'unexpected url'). In
|
||||||
// order to try to prevent the breakage of the app, try to fail on an
|
// order to try to prevent the breakage of the app, try to fail on an
|
||||||
// invalid URL as soon as possible.
|
// invalid URL as soon as possible.
|
||||||
const { hostname, pathname, protocol } = new URL(url);
|
const { hostname, pathname, protocol } = new URL(url);
|
||||||
|
|
||||||
// XXX The standard URL implementation should throw an Error if the
|
// XXX The standard URL implementation should throw an Error if the
|
||||||
// specified URL is relative. Unfortunately, the polyfill used on
|
// specified URL is relative. Unfortunately, the polyfill used on
|
||||||
// react-native does not.
|
// react-native does not.
|
||||||
if (!hostname || !pathname || !protocol) {
|
if (!hostname || !pathname || !protocol) {
|
||||||
reject(`unexpected url: ${url}`);
|
throw new Error(`unexpected url: ${url}`);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
const controller = new AbortController();
|
||||||
|
const signal = controller.signal;
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
const response = await fetch(url, { signal });
|
||||||
|
|
||||||
|
// If the timeout hits the above will raise AbortError.
|
||||||
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
switch (response.status) {
|
||||||
|
case 200: {
|
||||||
|
const txt = await response.text();
|
||||||
|
|
||||||
|
if (skipEval) {
|
||||||
|
return txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetch_ = fetch(url, { method: 'GET' });
|
return eval.call(window, txt); // eslint-disable-line no-eval
|
||||||
|
}
|
||||||
// The implementation of fetch provided by react-native is based on
|
default:
|
||||||
// XMLHttpRequest. Which defines timeout as an unsigned long with
|
throw new Error(`loadScript error: ${response.statusText}`);
|
||||||
// default value 0, which means there is no timeout.
|
}
|
||||||
if (timeout) {
|
|
||||||
// FIXME I don't like the approach with timeoutPromise because:
|
|
||||||
//
|
|
||||||
// * It merely abandons the underlying XHR and, consequently, opens
|
|
||||||
// us to potential issues with NetworkActivityIndicator which
|
|
||||||
// tracks XHRs.
|
|
||||||
//
|
|
||||||
// * @paweldomas also reported that timeouts seem to be respected by
|
|
||||||
// the XHR implementation on iOS. Given that we have
|
|
||||||
// implementation of loadScript based on fetch and XHR (in an
|
|
||||||
// earlier revision), I don't see why we're not using an XHR
|
|
||||||
// directly on iOS.
|
|
||||||
//
|
|
||||||
// * The approach of timeoutPromise I found on the Internet is to
|
|
||||||
// directly use XHR instead of fetch and abort the XHR on timeout.
|
|
||||||
// Which may deal with the NetworkActivityIndicator at least.
|
|
||||||
fetch_ = timeoutPromise(fetch_, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch_
|
|
||||||
.then(response => {
|
|
||||||
switch (response.status) {
|
|
||||||
case 200:
|
|
||||||
return response.responseText || response.text();
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw response.statusText;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(responseText => {
|
|
||||||
eval.call(window, responseText); // eslint-disable-line no-eval
|
|
||||||
})
|
|
||||||
.then(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue