in components/base-ui/packages/base-ui/lib/helpers/api.js [76:186]
function fetchJson(url, options = {}, retryCount = 0) {
// see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
let isOk = false;
let httpStatus;
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
};
const body = '';
const merged = {
method: 'GET',
cache: 'no-cache',
mode: config.fetchMode,
redirect: 'follow',
body,
...options,
headers: { ...headers, ...options.headers },
};
if (merged.method === 'GET') delete merged.body; // otherwise fetch will throw an error
if (merged.params) {
// if query string parameters are specified then add them to the URL
// The merged.params here is just a plain JavaScript object with key and value
// For example {key1: value1, key2: value2}
// Get keys from the params object such as [key1, key2] etc
const paramKeys = _.keys(merged.params);
// Filter out params with undefined or null values
const paramKeysToPass = _.filter(paramKeys, key => !_.isNil(_.get(merged.params, key)));
const query = _.map(
paramKeysToPass,
key => `${encodeURIComponent(key)}=${encodeURIComponent(_.get(merged.params, key))}`,
).join('&');
url = query ? `${url}?${query}` : url;
// Delete merged.params after they are added to the url as query string params
// This is required otherwise, if the call fails for some reason (e.g., time out) the same query string params
// will be added once again to the URL causing duplicate params being passed in.
// For example, if the merge.params = { param1: 'value1', param2: 'value2' }
// The url will become something like `https://some-host/some-path?param1=value1¶m2=value2`
// If we do not delete "merged.params" here and if the call is retried (with a recursive call to "fetchJson") due
// to timeout or any other issue, the url will then become
// `https://some-host/some-path?param1=value1¶m2=value2?param1=value1¶m2=value2`
delete merged.params;
}
return Promise.resolve()
.then(() => fetch(url, merged))
.catch(err => {
// this will capture network/timeout errors, because fetch does not consider http Status 5xx or 4xx as errors
if (retryCount < config.maxRetryCount) {
let backoff = retryCount * retryCount;
if (backoff < 1) backoff = 1;
return Promise.resolve()
.then(() => console.log(`Retrying count = ${retryCount}, Backoff = ${backoff}`))
.then(() => delay(backoff))
.then(() => fetchJson(url, options, retryCount + 1));
}
throw parseError(err);
})
.then(response => {
isOk = response.ok;
httpStatus = response.status;
return response;
})
.then(response => {
if (_.isFunction(response.text)) return response.text();
return response;
})
.then(text => {
let json;
try {
if (_.isObject(text)) {
json = text;
} else {
json = JSON.parse(text);
}
} catch (err) {
if (httpStatus >= 400) {
if (httpStatus >= 501 && retryCount < config.maxRetryCount) {
let backoff = retryCount * retryCount;
if (backoff < 1) backoff = 1;
return Promise.resolve()
.then(() => console.log(`Retrying count = ${retryCount}, Backoff = ${backoff}`))
.then(() => delay(backoff))
.then(() => fetchJson(url, options, retryCount + 1));
}
throw parseError({
message: text,
status: httpStatus,
});
} else {
throw parseError(new Error('The server did not return a json response.'));
}
}
return json;
})
.then(json => {
if (_.isBoolean(isOk) && !isOk) {
throw parseError({ ...json, status: httpStatus });
} else {
return json;
}
});
}