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((_, 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 { return Promise.race([ successfulFetch(url, options), new Promise((_, 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; }