跳到主要内容

Fetch

fetch 是浏览器和现代运行时里最基础的请求能力。它的优点很明显:原生、自带、够轻。真正的问题不在能不能发请求,而在于业务项目里怎么把它用稳。

一个最基础的请求

const res = await fetch('/api/users')

if (!res.ok) {
throw new Error(`request failed: ${res.status}`)
}

const data = await res.json()

这里最容易漏掉的一件事是:fetch 默认不会因为 404500 自动抛错。只要网络请求成功发出并收到响应,它就会 resolve。

所以项目里通常都会自己补一层 res.ok 判断。

GET、POST 和 JSON

await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Tom',
}),
})

超时怎么做

fetch 没有内置 timeout,常见做法是配合 AbortController

const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 8000)

try {
const res = await fetch('/api/users', {
signal: controller.signal,
})
const data = await res.json()
return data
} finally {
clearTimeout(timer)
}

取消请求

页面切换、输入搜索、组件卸载时,取消请求很常见。

const controller = new AbortController()

fetch('/api/search?q=react', {
signal: controller.signal,
})

controller.abort()

常见封装方式

项目里一般不会每次都手写 fetch + ok 判断 + json 解析 + timeout。更常见的做法是包一层。

interface RequestOptions extends RequestInit {
timeout?: number
}

export async function request<T>(url: string, options: RequestOptions = {}) {
const { timeout = 8000, headers, ...rest } = options
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), timeout)

try {
const res = await fetch(url, {
...rest,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
...headers,
},
})

if (!res.ok) {
throw new Error(`request failed: ${res.status}`)
}

return (await res.json()) as T
} finally {
clearTimeout(timer)
}
}

Fetch 更适合什么场景

  • 项目不大
  • 请求层需求不复杂
  • 更希望维持原生能力,不额外引入库
  • 在 Node、浏览器、Edge 运行时之间统一使用同一套能力

什么时候会想换到 Axios 或 Query 层

  • 统一拦截器需求变多
  • 上传下载进度控制更重
  • 项目里要做大量统一错误处理
  • 需要系统级缓存、失效和数据同步

这时候通常会往 AxiosTanStack Query 再上一级。