187 lines
4.7 KiB
TypeScript
187 lines
4.7 KiB
TypeScript
const TIMEOUT_ERROR = new Error('timeout')
|
|
|
|
const hexRe = /^[0-9A-Fa-f]+$/gu
|
|
|
|
/**
|
|
* Execute fetch and verify that the response was successful.
|
|
*
|
|
* @param request - Request information.
|
|
* @param options - Fetch options.
|
|
* @returns The fetch response.
|
|
*/
|
|
export async function successfulFetch(request: string, options?: RequestInit) {
|
|
const response = await fetch(request, options)
|
|
if (!response.ok) {
|
|
throw new Error(`Fetch failed with status '${response.status}' for request '${request}'`)
|
|
}
|
|
return response
|
|
}
|
|
|
|
/**
|
|
* Execute fetch and return object response.
|
|
*
|
|
* @param request - The request information.
|
|
* @param options - The fetch options.
|
|
* @returns The fetch response JSON data.
|
|
*/
|
|
export async function handleFetch(request: string, options?: RequestInit) {
|
|
const response = await successfulFetch(request, options)
|
|
const object = await response.json()
|
|
return object
|
|
}
|
|
|
|
/**
|
|
* Execute fetch and return object response, log if known error thrown, otherwise rethrow error.
|
|
*
|
|
* @param request - the request options object
|
|
* @param request.url - The request url to query.
|
|
* @param request.options - The fetch options.
|
|
* @param request.timeout - Timeout to fail request
|
|
* @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context
|
|
* @returns The fetch response JSON data or undefined (if error occurs).
|
|
*/
|
|
export async function fetchWithErrorHandling({
|
|
url,
|
|
options,
|
|
timeout,
|
|
errorCodesToCatch,
|
|
}: {
|
|
url: string
|
|
options?: RequestInit
|
|
timeout?: number
|
|
errorCodesToCatch?: number[]
|
|
}) {
|
|
let result
|
|
try {
|
|
if (timeout) {
|
|
result = Promise.race([
|
|
await handleFetch(url, options),
|
|
new Promise<Response>((_, reject) =>
|
|
setTimeout(() => {
|
|
reject(TIMEOUT_ERROR)
|
|
}, timeout),
|
|
),
|
|
])
|
|
} else {
|
|
result = await handleFetch(url, options)
|
|
}
|
|
} catch (e) {
|
|
logOrRethrowError(e, errorCodesToCatch)
|
|
}
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Fetch that fails after timeout.
|
|
*
|
|
* @param url - Url to fetch.
|
|
* @param options - Options to send with the request.
|
|
* @param timeout - Timeout to fail request.
|
|
* @returns Promise resolving the request.
|
|
*/
|
|
export async function timeoutFetch(url: string, options?: RequestInit, timeout = 500): Promise<Response> {
|
|
return Promise.race([
|
|
successfulFetch(url, options),
|
|
new Promise<Response>((_, reject) =>
|
|
setTimeout(() => {
|
|
reject(TIMEOUT_ERROR)
|
|
}, timeout),
|
|
),
|
|
])
|
|
}
|
|
|
|
/**
|
|
* Utility method to log if error is a common fetch error and otherwise rethrow it.
|
|
*
|
|
* @param error - Caught error that we should either rethrow or log to console
|
|
* @param codesToCatch - array of error codes for errors we want to catch and log in a particular context
|
|
*/
|
|
function logOrRethrowError(error: any, codesToCatch: number[] = []) {
|
|
if (!error) {
|
|
return
|
|
}
|
|
|
|
const includesErrorCodeToCatch = codesToCatch.some(code =>
|
|
error.message.includes(`Fetch failed with status '${code}'`),
|
|
)
|
|
|
|
if (
|
|
error instanceof Error &&
|
|
(includesErrorCodeToCatch || error.message.includes('Failed to fetch') || error === TIMEOUT_ERROR)
|
|
) {
|
|
console.error(error)
|
|
} else {
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成 key1=val1&key2=val2的字符串
|
|
* @param {object} data 需要处理的对象
|
|
* @param {boolean} sort 是否按key生序重排
|
|
* @param {boolean} ignoreNull 是否过滤空值(空格或者null值不参与拼接)
|
|
* @param splitChar 连接的字符, 默认是&
|
|
* @param equalChar =
|
|
*/
|
|
export function generateKVStr({
|
|
data = {},
|
|
sort = false,
|
|
encode = false,
|
|
ignoreNull = true,
|
|
splitChar = "&",
|
|
equalChar = "=",
|
|
uri = "",
|
|
}: {
|
|
data?: any;
|
|
sort?: boolean;
|
|
encode?: boolean;
|
|
ignoreNull?: boolean;
|
|
splitChar?: string;
|
|
equalChar?: string;
|
|
uri?: string;
|
|
}) {
|
|
const keys = Object.keys(data);
|
|
sort && keys.sort();
|
|
let result = "";
|
|
let i = 0;
|
|
for (let key of keys) {
|
|
if (ignoreNull && !data[key]) {
|
|
continue;
|
|
}
|
|
if (i++ > 0) result += splitChar;
|
|
if (encode) {
|
|
result += `${key}${equalChar}${encodeURIComponent(data[key])}`;
|
|
} else {
|
|
result += `${key}${equalChar}${data[key]}`;
|
|
}
|
|
}
|
|
if (uri) {
|
|
const joinChar = uri.search(/\?/) === -1 ? "?" : "&";
|
|
result = uri + joinChar + result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 将key1=val&key2=val的字符串组装成对象
|
|
* @param str key1=val&key2=val的字符串
|
|
* @param splitChar 连接的字符, 默认是&
|
|
* @param equalChar =
|
|
*/
|
|
export function keyValToObject(
|
|
str: string,
|
|
splitChar: string = "&",
|
|
equalChar = "="
|
|
): {} {
|
|
let result: any = {};
|
|
if (!str) {
|
|
return result;
|
|
}
|
|
let arrs = str.split(splitChar);
|
|
for (let sub of arrs) {
|
|
let subArr = sub.split(equalChar);
|
|
result[subArr[0]] = subArr[1];
|
|
}
|
|
return result;
|
|
}
|