SkyBlog

©2020 - 2026 By Flysky
框架Next.js|主题Shadcn
Built by vercel on Fri, 23 Jan 2026 09:54:29 GMT

fetch 封装

本博客以前使用的方法,但现在已经转向 Elysia 了,所以备份记录下

这篇文章发布于 2026年01月12日,星期一,09:03。阅读 ? 次,? 条评论

JSON 判断放在前面是因为接口或许不是按照标准 HTTP 返回状态码

Response 接口的只读属性 ok 包含一个布尔值,表明响应是否成功(状态码在 200-299 范围内)

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
      throw new Error(JSON.stringify(data))
    }
 
    if (!res.ok) {
      throw new Error(JSON.stringify(res.statusText))
    }
 
    // Text
    if (contentType?.includes('text/plain')) {
      return await res.text()
    }
 
    // Blob
    return await res.blob()
  } catch (error) {
    if (retry > 1) {
      await delay(200)
      return core(promise, retry - 1)
    }
 
    const message = JSON.parse((error as Error).message)
 
    const text = typeof message == 'string' ? message : JSON.stringify(message, null, 2)
 
    if (isBrowser()) {
      if (window.location.pathname.startsWith('/dashboard')) {
        toast.error(text, { id: text, richColors: true })
      }
    }
 
    return Promise.reject(text)
  }
}
 
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)
}

下一页

Next.js 中的缓存