不要再用axios 和 fetch了,试试更强大的up-fetch

up-fetch

假如你已经厌倦了在每个项目中都编写 fetch 封装程序,那么,up-fetch 或许就是你的救命稻草。
up-fetch 是一个仅有 1kb 大小,同时集成了一些合理配置的Fetch API 工具。

up-fetch GitHub地址

特点

  • 🚀 轻量 - 生产版本只有 1KB,没有其它依赖
  • 🤩 简单 - 基于 Fetch API,扩展了配置项,并集成了默认配置
  • 🎯 直观 - params 和 body 可以是普通对象,同时,Response 开箱即用
  • 🔥 灵活 - 复杂的场景下,支持自定义序列化和解析策略
  • 💫 复用 - 可创建带自定义默认项的实例
  • 💪 强类型 - 优秀的类型推断和自动补全能力
  • 🤯 校验适配器 -(可选)使用 zod 或 valibot 校验数据,以获得最大程度上的类型安全性
  • 👻 异常默认抛出 - 当 response.ok 为 false 时
  • 😉 适用环境广发 - 所有现代浏览器、bun、node 18+、deno(使用 npm: 限定符)
  • 📦 树摇优化 - 只打包使用到的内容

快速上手

npm i up-fetch

创建一个 upfetch 实例

import { up } from 'up-fetch'

const upfetch = up(fetch)

发送请求

const todo = await upfetch('https://a.b.c', {
   method: 'POST',
   body: { hello: 'world' },
})

可以为所有的请求设定一些默认选项。
默认项支持动态设定,在每次请求生成时获取,这对认证场景有很大帮助。

const upfetch = up(fetch, () => ({
   baseUrl: 'https://a.b.c',
   headers: { Authorization: localStorage.getItem('bearer-token') },
}))

因为 up-fetch 是基于 Fetch API 进行扩展的超集,所以任何 Fetch API 支持的特性,up-fetch 也都可以兼容。

// baseUrl 和 Authorization header 可以不被设定
const todo = await upfetch('/todos', {
   method: 'POST',
   body: { title: 'Hello World' },
   params: { some: 'query params' },
   headers: { 'X-Header': 'Another header' },
   signal: AbortSignal.timeout(5000),
   cache: 'no-store',
})

同样,也支持其它任何基于 Fetch API 规范实现的第三方工具,例如 undici 或者 node-fetch

import { fetch } from 'undici'

const upfetch = up(fetch)

特性

✔️ 为 upfetch 实例设定默认项

up-fetch 的默认行为可以完全自定义

const upfetch = up(fetch, () => ({
   baseUrl: 'https://a.b.c',
   headers: { 'X-Header': 'hello world' },
}))

可以查看完整的 options 列表

✔️ URL params 可以是对象

// 普通fetch
fetch(`https://a.b.c/?search=${search}&skip=${skip}&take=${take}`)

// up-fetch
upfetch('https://a.b.c', {
   params: { search, skip, take },
})

✔️ baseUrl 选项

在创建upfetch实例时,设定 baseUrl

export const upfetch = up(fetch, () => ({
   baseUrl: 'https://a.b.c',
}))

后续在每次发送请求时,都可以不用再带上 baseUrl

const todos = await upfetch('/todos')

✔️ 自动解析 Response

解析方法支持自定义 parseResponse

// 普通fetch
const response = await fetch('https://a.b.c')
const todos = await response.json()

// upfetch
const todos = await upfetch('https://a.b.c')

✔️ 异常默认抛出

假如 response.okfalse,会抛出 ResponseError 异常。

解析后的异常信息可以通过 error.data获取。
原始的 response 数据可以通过 error.response 获取。
用于 api 调用的选项可通过error.options获取。

import { isResponseError } from 'up-fetch'
import { upfetch } from '...'

try {
   await upfetch('https://a.b.c')
} catch (error) {
   if (isResponseError(error)) {
      console.log(error.data)
      console.log(error.response.status)
   } else {
      console.log('Request error')
   }
}

✔️ body 可设定为 json 格式

如果 body 是可转换为JSON格式的 object 或 数组, 请求头中会自动设定 'Content-Type': 'application/json'
普通 object, 数组和带有 toJSON 方法的类实例都认为是可转成JSON格式的数据类型。

// before
fetch('https://a.b.c', {
   method: 'POST',
   headers: { 'Content-Type': 'application/json' },
   body: JSON.stringify({ post: 'Hello World' }),
})

// after
upfetch('https://a.b.c', {
   method: 'POST',
   body: { post: 'Hello World' },
})

✔️ 数据校验

up-fetch 内部集成了一些基于 zodvalibot 的适配器

首先需要安装 zodvalibot

npm i zod
# or
npm i valibot

接下来就可以使用内部集成的一些数据校验 helper,这些 helper 方法支持 Tree Shaking 。

zod 示例:

import { z } from 'zod'
import { withZod } from 'up-fetch' 

// ...create or import your upfetch instance

const todo = await upfetch('/todo/1', {
   parseResponse: withZod(
      z.object({
         id: z.number(),
         title: z.string(),
         description: z.string(),
         createdOn: z.string(),
      }),
   ),
})
// the type of todo is { id: number, title: string, description: string, createdOn: string}

valibot 示例:

import { object, string, number } from 'zod'
import { withValibot } from 'up-fetch'

// ...create or import your upfetch instance

const todo = await upfetch('/todo/1', {
   parseResponse: withValibot(
      object({
         id: number(),
         title: string(),
         description: string(),
         createdOn: string(),
      }),
   ),
})
// the type of todo is { id: number, title: string, description: string, createdOn: string}

如果出现错误,适配器将抛出异常。可以通过[onParsingError](#onParsingError)来监听这些错误信息。
同样,适配器也可以用于parseResponseError

✔️ 拦截器

可以为所有的请求设定拦截器。

const upfetch = up(fetch, () => ({
   onBeforeFetch: (options) => console.log('Before fetch'),
   onSuccess: (data, options) => console.log(data),
   onResponseError: (error, options) => console.log(error),
   onRequestError: (error, options) => console.log(error),
   onParsingError: (error, options) => console.log(error),
}))

也可以为单个请求设定拦截器

upfetch('/todos', {
   onBeforeFetch: (options) => console.log('Before fetch'),
   onSuccess: (todos, options) => console.log(todos),
   onResponseError: (error, options) => console.log(error),
   onRequestError: (error, options) => console.log(error),
   onParsingError: (error, options) => console.log(error),
})

了解更多.

✔️ timeout

值得一提的是,由于AbortSignal.timeout方法现在已经非常普及,因此up-petch不提供任何 timeout 选项。

upfetch('/todos', {
   signal: AbortSignal.timeout(5000),
})

➡️ 示例

💡 认证

由于默认项是在请求时获取的, 所以 Authentication header 可以在 up 方法中定义。

import { up } from 'up-fetch'

const upfetch = up(fetch, () => ({
   headers: { Authentication: localStorage.getItem('bearer-token') },
}))

localStorage.setItem('bearer-token', 'Bearer abcdef123456')
upfetch('/profile') // Authenticated request

localStorage.removeItem('bearer-token')
upfetch('/profile') // Non authenticated request
// ❌ 不要在 `up` 方法之外读取 storage / cookies 

// bearerToken 从不会改变
const bearerToken = localStorage.getItem('bearer-token')

const upfetch = up(fetch, () => ({
   headers: { Authentication: bearerToken },
}))
// ✅ `up` 方法的第二个参数如果是一个函数,会在每次请求时被调用用于获取 **默认项** 数据

// 每次请求时都去 localStorage 中读取数据
const upfetch = up(fetch, () => ({
   headers: { Authentication: localStorage.getItem('bearer-token') },
}))

cookies 同理。

💡 错误处理

response.okfalse 时,up-fetch 抛出 ResponseError 异常。

解析后的异常信息可以通过 error.data获取。
原始的 response status 可以通过 error.response.status 获取。
用于 api 调用的选项可通过error.options获取。

type guard isResponseError 可以用于判断当前 error 是否是 ResponseError

import { upfetch } from '...'
import { isResponseError } from 'up-fetch'

// with try/catch
try {
   return await upfetch('https://a.b.c')
} catch (error) {
   if (isResponseError(error)) {
      console.log(error.name)
      console.log(error.message)
      console.log(error.data)
      console.log(error.response.status)
      console.log(error.options)
   } else {
      console.log(error.name)
      console.log(error.message)
   }
}

// with Promise.catch
upfetch('https://a.b.c').catch((error) => {
   if (isResponseError(error)) {
      console.log(error.name)
      console.log(error.message)
      console.log(error.data)
      console.log(error.response.status)
      console.log(error.options)
   } else {
      console.log(error.name)
      console.log(error.message)
   }
})

up-fetch 还提供了一些 listener, 对日志记录有很大帮助。

import { up } from 'up-fetch'
import { log } from './my-logging-service'

const upfetch = up(fetch, () => ({
   onResponseError(error) {
      log.responseError(error)
   },
   onRequestError(error) {
      log.requestError(error)
   },
}))

upfetch('/fail-to-fetch')
💡 删除默认项

只需要设定值为 undefined 即可。

import { up } from 'up-fetch'

const upfetch = up(fetch, () => ({
   cache: 'no-store',
   params: { expand: true, count: 1 },
   headers: { Authorization: localStorage.getItem('bearer-token') },
}))

upfetch('https://a.b.c', {
   cache: undefined, // remove cache
   params: { expand: undefined }, // only remove `expand` from the params
   headers: undefined, // remove all headers
})
💡 根据特定条件选择是否覆盖默认值

有时候可能需要有条件地覆盖 up 方法中提供的默认选项。这对Javascript来说,有点麻烦:

import { up } from 'up-fetch'

const upfetch = up(fetch, () => ({
   headers: { 'X-Header': 'value' }
}))

❌ 不要
// if `condition` is false, the header will be deleted
upfetch('https://a.b.c', {
   headers: { 'X-Header': condition ? 'newValue' : undefined }
})

为解决上述问题, 当up 方法第二个参数是函数类型时,upfetch 提供 upOptions 作为其参数.
upOptions 类型严格 (const 泛型)

OK
upfetch('https://a.b.c', (upOptions) => ({
   headers: { 'X-Header': condition ? 'newValue' : upOptions.headers['X-Header'] }
}))
💡 Next.js 路由

因为 up-fetch 基于 fetch API 进行扩展, 所以 Next.js 特定的 fetch options 也适用于 up-fetch.

设定默认缓存策略

import { up } from 'up-fetch'

const upfetch = up(fetch, () => ({
   next: { revalidate: false },
}))

特定请求覆盖

upfetch('/posts', {
   next: { revalidate: 60 },
})

➡️ Types

请参阅类型定义取更多详细信息

➡️ API

除了[body](#body)之外,所有选项都可以在upupfetch实例上设置。

// set defaults for the instance
const upfetch = up(fetch, () => ({
   baseUrl: 'https://a.b.c',
   cache: 'no-store',
   headers: { Authorization: `Bearer ${token}` },
}))

// override the defaults for a specific call
upfetch('/todos', {
   baseUrl: 'https://x.y.z',
   cache: 'force-cache',
})

fetch API 的基础上,upfetch 新增了如下选项.

<baseUrl>

Type: string

设定 baseUrl

示例:

const upfetch = up(fetch, () => ({
   baseUrl: 'https://a.b.c',
}))

// make a GET request to 'https://a.b.c/id'
upfetch('/id')

// change the baseUrl for a single request
upfetch('/id', { baseUrl: 'https://x.y.z' })

<params>

Type: { [key: string]: any }

查询参数
upupfetch 方法中分别定义的参数会被 shallowly merged.
默认情况下,仅支持非嵌套对象。有关嵌套对象的处理,可以借助[serializeParams](#serializeParams)选项。

示例:

const upfetch = up(fetch, () => ({
   params: { expand: true },
}))

// `expand` can be omitted
// ?expand=true&page=2&limit=10
upfetch('https://a.b.c', {
   params: { page: 2, limit: 10 },
})

// override the `expand` param
// ?expand=false&page=2&limit=10
upfetch('https://a.b.c', {
   params: { page: 2, limit: 10, expand: false },
})

// delete `expand` param
// ?expand=false&page=2&limit=10
upfetch('https://a.b.c', {
   params: { expand: undefined },
})

// conditionally override the expand param `expand` param
// ?expand=false&page=2&limit=10
upfetch('https://a.b.c', (upOptions) => ({
   params: { expand: isTruthy ? true : upOptions.params.expand },
}))

<headers>

Type: HeadersInit | Record<string, string | number | null | undefined>

与 fetch API headers 兼容,但有更广泛的类型支持.
upupfetch 中分别定义的 header 同样会被 shallowly merged. \

示例:

const upfetch = up(fetch, () => ({
   headers: { Authorization: 'Bearer ...' },
}))

// the request will have both the `Authorization` and the `Test-Header` headers
upfetch('https://a.b.c', {
   headers: { 'Test-Header': 'test value' },
})

// override the `Authorization` header
upfetch('https://a.b.c', {
   headers: { Authorization: 'Bearer ...2' },
})

// delete the `Authorization` header
upfetch('https://a.b.c', {
   headers: { Authorization: null }, // undefined also works
})

// conditionally override the `Authorization` header
upfetch('https://a.b.c', (upOptions) => ({
   headers: {
      Authorization: isTruthy ? 'Bearer ...3' : upOptions.headers.val,
   },
}))

<body>

Type: BodyInit | JsonifiableObject | JsonifiableArray | null

PS: 这个选项在 up 方法中不可用🚫。

设定请求中的 body.
可以是任何类型的数据.
具体信息可以参考serializeBody.

示例:

upfetch('/todos', {
   method: 'POST',
   body: { hello: 'world' },
})

<serializeParams>

Type: (params: { [key: string]: any } ) => string

自定义 params 的序列化方法.
默认仅支持 非嵌套的 objects.

示例:

import qs from 'qs'

// add support for nested objects using the 'qs' library
const upfetch = up(fetch, () => ({
   serializeParams: (params) => qs.stringify(params),
}))

// ?a[b]=c
upfetch('https://a.b.c', {
   params: { a: { b: 'c' } },
})

<serializeBody>

Type: (body: JsonifiableObject | JsonifiableArray) => string

Default: JSON.stringify

自定义 body 序列化方法.
当 body 是普通对象、数组或具有 toJSON 方法的类实例时,将被传递给 serializeBody,其他类型保持不变

示例:

import stringify from 'json-stringify-safe'

// Add support for circular references.
const upfetch = up(fetch, () => ({
   serializeBody: (body) => stringify(body),
}))

upfetch('https://a.b.c', {
   body: { now: 'imagine a circular ref' },
})

<parseResponse>

Type: ParseResponse<TData> = (response: Response, options: ComputedOptions) => Promise<TData>

自定义 Response 解析方法.
默认情况下,先解析成json ,其次选择 text 格式。

这个选项最好配合 validation adapter 使用。

示例:

// create a fetcher for blobs
const fetchBlob = up(fetch, () => ({
   parseResponse: (res) => res.blob(),
}))

// disable the default parsing
const upfetch = up(fetch, () => ({
   parseResponse: (res) => res,
}))

添加校验适配器:

import { z } from 'zod'
import { withZod } from 'up-fetch'

// ...create or import your upfetch instance

const todo = await upfetch('/todo/1', {
   parseResponse: withZod(
      z.object({
         id: z.number(),
         title: z.string(),
         description: z.string(),
         createdOn: z.string(),
      }),
   ),
})

<parseResponseError>

Type: ParseResponseError<TError> = (response: Response, options: ComputedOptions) => Promise<TError>

自定义 Response Error 的解析方法 (response.okfalse 时)
默认情况下抛出 ResponseError 异常

示例:

// throw a `CustomResponseError` when `response.ok` is `false`
const upfetch = up(fetch, () => ({
   parseResponseError: (res) => new CustomResponseError(res),
}))

parseResponse 可以用于 validation adapter

<onSuccess>

Type: <TData>(data: TData, options: ComputedOptions) => void

response.oktrue 时调用

示例:

const upfetch = up(fetch, () => ({
   onSuccess: (data, options) => console.log('2nd'),
}))

upfetch('https://a.b.c', {
   onSuccess: (data, options) => console.log('1st'),
})

<onResponseError>

Type: <TResponseError>(error: TResponseError, options: ComputedOptions) => void

当响应异常抛出时调用 (response.okfalse).

示例:

const upfetch = up(fetch, () => ({
   onResponseError: (error, options) => console.log('Response error', error),
}))

upfetch('https://a.b.c', {
   onResponseError: (error, options) => console.log('Response error', error),
})

<onRequestError>

Type: (error: Error, options: ComputedOptions) => void

当请求失败时调用 (没有收到服务器响应).

示例:

const upfetch = up(fetch, () => ({
   onRequestError: (error, options) => console.log('Request error', error),
}))

upfetch('https://a.b.c', {
   onRequestError: (error, options) => console.log('Request error', error),
})

<onParsingError>

Type: (error: any, options: ComputedOptions) => void

parseResponseparseResponseError 异常产生时调用.
这在使用 validation adapter 时非常有用

示例:

import { z } from 'zod'
import { withZod } from 'up-fetch'

const upfetch = up(fetch, () => ({
   onParsingError: (error, options) => console.log('Validation error', error),
}))

upfetch('https://a.b.c', {
   onParsingError: (error, options) => console.log('Validation error', error),
   parseResponse: withZod(
      z.object({
         id: z.number(),
         title: z.string(),
         description: z.string(),
         createdOn: z.string(),
      }),
   ),
})

<onBeforeFetch>

Type: (options: ComputedOptions) => void

请求发送之前被调用.

示例:

const upfetch = up(fetch, () => ({
   onBeforeFetch: (options) => console.log('2nd'),
}))

upfetch('https://a.b.c', {
   onBeforeFetch: (options) => console.log('1st'),
})

➡️ 兼容性

  • ✅ All modern browsers
  • ✅ Bun
  • ✅ Node 18+
  • ✅ Deno (with the npm: specifier)
  • 26
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值