本博客以前使用的方法,但现在已经转向 Elysia 了,所以备份记录下
这篇文章发布于 2026年01月12日,星期一,09:03。阅读 ? 次,? 条评论
import { cloneDeep, delay, isBrowser, withTimeout } from 'es-toolkit'
import { toast } from 'sonner'
const core = async (promise: () => Promise<Response>, retry: number) => {
try {
const res = await promise()
const contentType = res.headers.get('Content-Type')
// json
if (contentType?.includes('application/json')) {
const data = await res.json()
if (res.ok) return data
if ('message' in data) {
throw new Error(JSON.stringify(data.message))
}
throw new Error(JSON.stringify(data))
}
if (!res.ok) {
throw new Error(`error code ${res.status}`)
}
// text
if (contentType?.includes('text')) {
return await res.text()
}
// blob
return await res.blob()
} catch (error) {
if (retry > 1) {
await delay(200)
return core(promise, retry - 1)
}
console.error(error)
const text = JSON.parse((error as Error).message)
const message = typeof text == 'string' ? text : JSON.stringify(text, null, 2)
if (isBrowser()) {
if (window.location.pathname.startsWith('/dashboard')) {
toast.error(message, { closeButton: true, id: message, richColors: true })
}
}
return Promise.reject(message)
}
}
interface FetchOptions extends Omit<RequestInit, 'body' | 'headers' | 'method'> {
body?: any
headers?: Record<string, string>
method?: Method
}
/**
* 封装的基础请求方法
*/
export const ifetch = async <T = any>(input: RequestInfo | URL, { body, headers = {}, ...init }: FetchOptions = {}): Promise<T> => {
headers = cloneDeep(headers) // 避免修改原对象
if (!(body instanceof FormData)) {
if (!headers['Content-Type']) {
headers['Content-Type'] = 'application/json'
}
if (headers['Content-Type'].includes('application/json')) {
body = JSON.stringify(body)
}
}
if (typeof input == 'string') {
input = input.replace(/(?<!:)\/{2,}/g, '/')
if (input.endsWith('/')) {
input = input.slice(0, -1)
}
}
return core(async () => {
return withTimeout(async () => fetch(input, { body, headers, ...init }), 5000)
}, 3)
}