You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决TypeScript下Axios+createAsyncThunk错误类型定义与4xx捕获问题?

问题1:4xx错误无法捕获的原因

你当前的Axios响应拦截器做了特殊处理:当响应状态为404时,你直接返回了普通对象{ status: error.response.status },而非返回Promise.reject()。这会导致404错误被作为正常的resolve结果返回,不会触发外层的catch代码块,自然无法被捕获。只有非404的错误会被你以Promise.reject()抛出,才能进入catch逻辑。

如果需要所有4xx/5xx错误都进入异常处理逻辑,修改拦截器的错误处理分支即可:

instance.interceptors.response.use(response => {
    return response;
}, function (error) {
    // 无论什么错误都返回reject,统一走异常逻辑
    return Promise.reject(error);
});

问题2:错误类型去除any的正确写法

你更新后的写法方向是对的,但可以进一步完善类型定义,更贴合业务场景:

第一步:先定义统一的API错误结构

先根据你的后端返回格式定义错误接口,方便全局复用:

// 可以放在types/api.ts这类全局类型文件里
interface ApiError {
  status: number;
  message?: string;
  data?: unknown;
}

第二步:完善createAsyncThunk的泛型定义

createAsyncThunk支持传入第三个泛型参数,用来指定rejectWithValue返回的错误类型,同时在catch块中用Axios自带的AxiosError类型做类型收窄,不需要用any:

import { createAsyncThunk } from '@reduxjs/toolkit'
import { AxiosError } from 'axios'
import { todosApi } from '../../api/todosApi'

// 定义返回值类型可以单独抽出来复用
interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

export const fetchTodosById = createAsyncThunk<
  Todo, // 返回值类型
  number, // 参数类型
  { rejectValue: ApiError } // reject时返回的错误类型
>('todos/getTodosbyId', async (id, { rejectWithValue }) => {
  try {
    // 你之前的代码多写了一层await,这里修正
    const response = await todosApi.getTodosById(id);
    return response.data
  } catch (error) {
    // 类型收窄,判断是否是Axios的错误实例
    if (error instanceof AxiosError) {
      return rejectWithValue({
        status: error.response?.status || 500,
        message: error.response?.data?.message || '请求失败',
        data: error.response?.data
      })
    }
    // 非Axios的未知错误兜底
    return rejectWithValue({
      status: 500,
      message: '未知错误'
    })
  }
})

第三步:修改Slice中的错误类型

因为我们已经指定了rejectWithValue的类型,所以在rejected回调中可以直接取action.payload作为错误值,类型就是你定义的ApiError:

import { createSlice, SerializedError } from '@reduxjs/toolkit'
import { fetchTodosById } from './todosActions'

interface todosState {
    todos: Todo | null,
    todosLoading: boolean;
    // 这里可以根据你的需求选择:如果所有错误都走rejectWithValue就只写ApiError,否则加上SerializedError兜底
    todosError: ApiError | SerializedError | null;
}

const initialState: todosState = {
    todos: null,
    todosLoading: false,
    todosError: null
}

const todosSlice = createSlice({
    name: 'todos',
    initialState,
    reducers: {},
    extraReducers: (builder) => {
        builder
            .addCase(fetchTodosById.pending, (state) => {
                state.todosLoading = true
                state.todosError = null
            })
            .addCase(fetchTodosById.fulfilled, (state, action) => {
                state.todosLoading = false
                state.todos = action.payload
            })
            .addCase(fetchTodosById.rejected, (state, action) => {
                state.todosLoading = false
                // 优先取你自己通过rejectWithValue返回的payload,否则取默认的error
                state.todosError = action.payload || action.error
            })
    }
})

export default todosSlice.reducer;

关于你更新后写法的说明

你去掉try/catch直接判断response.status的写法是适配你当前拦截器逻辑的(因为404被转为正常返回),但如果后续拦截器调整为所有错误都抛出reject,还是需要加上try/catch做异常捕获,避免代码崩溃。

内容的提问来源于stack exchange,提问作者loone96

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 19:24:04