Fetch
fetch 是浏览器和现代运行时里最基础的请求能力。它的优点很明显:原生、自带、够轻。真正的问题不在能不能发请求,而在于业务项目里怎么把它用稳。
一个最基础的请求
const res = await fetch('/api/users')
if (!res.ok) {
throw new Error(`request failed: ${res.status}`)
}
const data = await res.json()
这里最容易漏掉的一件事是:fetch 默认不会因为 404、500 自动抛错。只要网络请求成功发出并收到响应,它就会 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 层
- 统一拦截器需求变多
- 上传下载进度控制更重
- 项目里要做大量统一错误处理
- 需要系统级缓存、失效和数据同步
这时候通常会往 Axios 或 TanStack Query 再上一级。