@uppy/utils
Version:
Shared utility functions for Uppy Core and plugins maintained by the Uppy team.
98 lines (97 loc) • 3.06 kB
JavaScript
import NetworkError from './NetworkError.js';
import ProgressTimeout from './ProgressTimeout.js';
const noop = () => {};
/**
* Fetches data from a specified URL using XMLHttpRequest, with optional retry functionality and progress tracking.
*
* @param url The URL to send the request to.
* @param options Optional settings for the fetch operation.
*/
export function fetcher(url, options) {
if (options === void 0) {
options = {};
}
const {
body = null,
headers = {},
method = 'GET',
onBeforeRequest = noop,
onUploadProgress = noop,
shouldRetry = () => true,
onAfterResponse = noop,
onTimeout = noop,
responseType,
retries = 3,
signal = null,
timeout = 30000,
withCredentials = false
} = options;
// 300 ms, 600 ms, 1200 ms, 2400 ms, 4800 ms
const delay = attempt => 0.3 * 2 ** (attempt - 1) * 1000;
const timer = new ProgressTimeout(timeout, onTimeout);
function requestWithRetry(retryCount) {
if (retryCount === void 0) {
retryCount = 0;
}
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const xhr = new XMLHttpRequest();
const onError = error => {
if (shouldRetry(xhr) && retryCount < retries) {
setTimeout(() => {
requestWithRetry(retryCount + 1).then(resolve, reject);
}, delay(retryCount));
} else {
timer.done();
reject(error);
}
};
xhr.open(method, url, true);
xhr.withCredentials = withCredentials;
if (responseType) {
xhr.responseType = responseType;
}
signal == null || signal.addEventListener('abort', () => {
xhr.abort();
// Using DOMException for abort errors aligns with
// the convention established by the Fetch API.
reject(new DOMException('Aborted', 'AbortError'));
});
xhr.onload = async () => {
try {
await onAfterResponse(xhr, retryCount);
} catch (err) {
// This is important as we need to emit the xhr
// over the upload-error event.
err.request = xhr;
onError(err);
return;
}
if (xhr.status >= 200 && xhr.status < 300) {
timer.done();
resolve(xhr);
} else if (shouldRetry(xhr) && retryCount < retries) {
setTimeout(() => {
requestWithRetry(retryCount + 1).then(resolve, reject);
}, delay(retryCount));
} else {
timer.done();
reject(new NetworkError(xhr.statusText, xhr));
}
};
xhr.onerror = () => onError(new NetworkError(xhr.statusText, xhr));
xhr.upload.onprogress = event => {
timer.progress();
onUploadProgress(event);
};
if (headers) {
Object.keys(headers).forEach(key => {
xhr.setRequestHeader(key, headers[key]);
});
}
await onBeforeRequest(xhr, retryCount);
xhr.send(body);
});
}
return requestWithRetry();
}