Skip to content

Commit ebbdf13

Browse files
committed
fix(axios): 修复当自定义拦截器解包响应数据时,请求返回 undefined 的问题
1 parent 0835838 commit ebbdf13

3 files changed

Lines changed: 144 additions & 10 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ethan-utils/axios": patch
3+
---
4+
5+
修复当自定义拦截器解包响应数据时,请求返回 undefined 的问题

packages/axios/index.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,24 @@ const formatError = <T>(error: unknown): BaseResponse<T> => {
9898
} as BaseResponse<T>;
9999
};
100100

101+
/**
102+
* 智能解包响应数据
103+
* 判断是否为 AxiosResponse,如果是则返回 data,否则直接返回 response
104+
*/
105+
const unwrapResponse = <T>(response: unknown): T => {
106+
if (
107+
response &&
108+
typeof response === "object" &&
109+
"data" in response &&
110+
"status" in response &&
111+
"headers" in response &&
112+
"config" in response
113+
) {
114+
return (response as any).data as T;
115+
}
116+
return response as T;
117+
};
118+
101119
/**
102120
* 创建 API 客户端,包含常用请求方法和原始请求方法(raw)
103121
* @param api Axios 实例
@@ -108,7 +126,7 @@ function createClient(api: AxiosInstance): ApiClient {
108126
/** GET 请求,直接返回后端数据,失败抛出异常 */
109127
async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
110128
const response = await api.get<T>(url, config);
111-
return response.data;
129+
return unwrapResponse<T>(response);
112130
},
113131
/** GET 标准请求,返回 BaseResponse 格式,错误会被转换为标准响应 */
114132
async getStandard<T, R extends BaseResponse<T> = BaseResponse<T>>(
@@ -117,7 +135,7 @@ function createClient(api: AxiosInstance): ApiClient {
117135
): Promise<R> {
118136
try {
119137
const response = await api.get<R>(url, config);
120-
return response.data;
138+
return unwrapResponse<R>(response);
121139
} catch (error: unknown) {
122140
return formatError<T>(error) as R;
123141
}
@@ -129,7 +147,7 @@ function createClient(api: AxiosInstance): ApiClient {
129147
config?: AxiosRequestConfig,
130148
): Promise<T> {
131149
const response = await api.post<T>(url, data, config);
132-
return response.data;
150+
return unwrapResponse<T>(response);
133151
},
134152
/** POST 标准请求,返回 BaseResponse 格式,错误会被转换为标准响应 */
135153
async postStandard<T, R extends BaseResponse<T> = BaseResponse<T>>(
@@ -139,7 +157,7 @@ function createClient(api: AxiosInstance): ApiClient {
139157
): Promise<R> {
140158
try {
141159
const response = await api.post<R>(url, data, config);
142-
return response.data;
160+
return unwrapResponse<R>(response);
143161
} catch (error: unknown) {
144162
return formatError<T>(error) as R;
145163
}
@@ -151,7 +169,7 @@ function createClient(api: AxiosInstance): ApiClient {
151169
config?: AxiosRequestConfig,
152170
): Promise<T> {
153171
const response = await api.put<T>(url, data, config);
154-
return response.data;
172+
return unwrapResponse<T>(response);
155173
},
156174
/** PUT 标准请求,返回 BaseResponse 格式,错误会被转换为标准响应 */
157175
async putStandard<T, R extends BaseResponse<T> = BaseResponse<T>>(
@@ -161,15 +179,15 @@ function createClient(api: AxiosInstance): ApiClient {
161179
): Promise<R> {
162180
try {
163181
const response = await api.put<R>(url, data, config);
164-
return response.data;
182+
return unwrapResponse<R>(response);
165183
} catch (error: unknown) {
166184
return formatError<T>(error) as R;
167185
}
168186
},
169187
/** DELETE 请求,直接返回后端数据,失败抛出异常 */
170188
async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
171189
const response = await api.delete<T>(url, config);
172-
return response.data;
190+
return unwrapResponse<T>(response);
173191
},
174192
/** DELETE 标准请求,返回 BaseResponse 格式,错误会被转换为标准响应 */
175193
async deleteStandard<T, R extends BaseResponse<T> = BaseResponse<T>>(
@@ -178,7 +196,7 @@ function createClient(api: AxiosInstance): ApiClient {
178196
): Promise<R> {
179197
try {
180198
const response = await api.delete<R>(url, config);
181-
return response.data;
199+
return unwrapResponse<R>(response);
182200
} catch (error: unknown) {
183201
return formatError<T>(error) as R;
184202
}
@@ -190,7 +208,7 @@ function createClient(api: AxiosInstance): ApiClient {
190208
config?: AxiosRequestConfig,
191209
): Promise<T> {
192210
const response = await api.patch<T>(url, data, config);
193-
return response.data;
211+
return unwrapResponse<T>(response);
194212
},
195213
/** PATCH 标准请求,返回 BaseResponse 格式,错误会被转换为标准响应 */
196214
async patchStandard<T, R extends BaseResponse<T> = BaseResponse<T>>(
@@ -200,7 +218,7 @@ function createClient(api: AxiosInstance): ApiClient {
200218
): Promise<R> {
201219
try {
202220
const response = await api.patch<R>(url, data, config);
203-
return response.data;
221+
return unwrapResponse<R>(response);
204222
} catch (error: unknown) {
205223
return formatError<T>(error) as R;
206224
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { expect, test, vi, describe, beforeEach } from "vitest";
2+
import { createRequest } from "../index";
3+
import axios from "axios";
4+
5+
// Mock axios
6+
vi.mock("axios", async () => {
7+
const actual = await vi.importActual<typeof import("axios")>("axios");
8+
const mockCreate = vi.fn(() => ({
9+
interceptors: {
10+
request: { use: vi.fn() },
11+
response: { use: vi.fn() },
12+
},
13+
get: vi.fn(),
14+
post: vi.fn(),
15+
put: vi.fn(),
16+
delete: vi.fn(),
17+
patch: vi.fn(),
18+
}));
19+
20+
const defaultAxios = actual.default;
21+
22+
return {
23+
...actual,
24+
default: {
25+
...defaultAxios,
26+
create: mockCreate,
27+
},
28+
};
29+
});
30+
31+
describe("请求返回值测试", () => {
32+
let mockAxiosInstance: any;
33+
34+
beforeEach(() => {
35+
mockAxiosInstance = {
36+
interceptors: {
37+
request: { use: vi.fn() },
38+
response: { use: vi.fn() },
39+
},
40+
get: vi.fn(),
41+
post: vi.fn(),
42+
put: vi.fn(),
43+
delete: vi.fn(),
44+
patch: vi.fn(),
45+
};
46+
(axios.create as any).mockReturnValue(mockAxiosInstance);
47+
});
48+
49+
test("请求应该正确返回数据", async () => {
50+
const data = { id: 1, name: "test" };
51+
// 模拟 get 返回结构 (完整的 AxiosResponse)
52+
mockAxiosInstance.get.mockResolvedValue({
53+
data,
54+
status: 200,
55+
headers: {},
56+
config: {},
57+
});
58+
59+
const client = createRequest(
60+
{
61+
baseURL: "http://localhost",
62+
},
63+
false,
64+
);
65+
66+
// 测试普通 get 请求
67+
const result = await client.get("/test");
68+
expect(result).toEqual(data);
69+
});
70+
71+
test("标准请求应该正确返回数据", async () => {
72+
const stdData = { code: 200, message: "ok", data: { id: 1 } };
73+
mockAxiosInstance.get.mockResolvedValue({
74+
data: stdData,
75+
status: 200,
76+
headers: {},
77+
config: {},
78+
});
79+
80+
const client = createRequest(
81+
{
82+
baseURL: "http://localhost",
83+
},
84+
false,
85+
);
86+
87+
const stdResult = await client.std.get("/std-test");
88+
expect(stdResult).toEqual(stdData);
89+
});
90+
91+
test("如果拦截器修改了返回值结构,应该能智能解包", async () => {
92+
const data = { id: 1, name: "test" };
93+
// 模拟用户添加了拦截器,直接返回了 data 而不是 response 对象
94+
mockAxiosInstance.get.mockResolvedValue(data);
95+
96+
const client = createRequest(
97+
{
98+
baseURL: "http://localhost",
99+
},
100+
false,
101+
);
102+
103+
// 这时候再调用 client.get
104+
// 内部实现是: const response = await api.get(); return unwrapResponse(response);
105+
// 此时 response = data
106+
// unwrapResponse(data) 应该返回 data (因为 data 不是 AxiosResponse)
107+
108+
const result = await client.get("/test");
109+
expect(result).toEqual(data);
110+
});
111+
});

0 commit comments

Comments
 (0)