Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions __tests__/axios.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createAxios } from '../src/axios';

describe('请求非 JSON 数据', () => {
it('XML 数据', async () => {
const axios = createAxios();
const res = await axios.get('https://httpbin.org/robots.txt');
expect(res.data).toBe('User-agent: *\nDisallow: /deny\n');
});
});

describe('请求 JSON 数据', () => {
it('普通数据', async () => {
const axios = createAxios();
const res = await axios.get('https://httpbin.org/json');
const expected = JSON.parse('{"slideshow":{"author":"Yours Truly","date":"date of publication","slides":[{"title":"Wake up to WonderWidgets!","type":"all"},{"items":["Why <em>WonderWidgets</em> are great","Who <em>buys</em> WonderWidgets"],"title":"Overview","type":"all"}],"title":"Sample Slide Show"}}');
expect(res.data).toMatchObject(expected);
});

it('带 bigint', async () => {
const axios = createAxios();
const obj = { id: 9223372036854775808n };
const res = await axios.post('https://httpbin.org/anything', obj);
expect(res.data.json).toMatchObject(obj);
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
},
"dependencies": {
"axios": "^1.6.2",
"is-plain-object": "^5.0.0",
"js-base64": "^3.7.5",
"json-bigint": "^1.0.0",
"mitt": "^3.0.1",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 44 additions & 20 deletions src/axios.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios, { AxiosResponse, CreateAxiosDefaults } from 'axios';
import axios, { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
import { isPlainObject } from 'is-plain-object';
import jsonBigint from 'json-bigint';
import { FanbookApiError } from './error';

Expand All @@ -7,21 +8,40 @@ export const bigintJsonParser = jsonBigint({
useNativeBigInt: true,
});

/** 测试 Content-Type 是否为 json 的正则表达式。 */
const isJsonContentType = /^application\/(.*\+)?json$/;
/**
* 转换请求响应
* @param data 响应体
* @returns 转换后的响应体
* 解析请求 bigint
* @param req 请求数据
* @returns 转换后的请求数据
*/
function parseResponseBigint(data: unknown) {
console.log(data);
if (typeof data === 'string') {
try { // 能转 JSON 就转
return bigintJsonParser.parse(data);
} catch { // 不能转原样返回
return data;
}
function requestBigintInterceptor(req: InternalAxiosRequestConfig) {
const type = req.headers['content-type'];
if (
(typeof type === 'string' && isJsonContentType.test(type))
|| isPlainObject(req.data)
) { // 是 JSON
req.data = bigintJsonParser.stringify(req.data);
req.headers['content-type'] ??= 'application/json';
}
return data;
return req;
}
/**
* 解析响应 bigint。
* @param res 响应数据
* @returns 转换后的响应数据
*/
function responseBigintInterceptor(res: AxiosResponse) {
// 非 json 不处理
const type = res.headers['content-type'];
if (typeof type !== 'string') return res;
if (!isJsonContentType.test(type)) return res;

// 带 bigint 解析 json,不行就算了
try {
res.data = bigintJsonParser.parse(res.data);
} catch {}
return res;
}

/**
Expand All @@ -30,13 +50,17 @@ function parseResponseBigint(data: unknown) {
* @returns Axios 实例
*/
export function createAxios(options?: CreateAxiosDefaults) {
let pipes = options?.transformResponse;
// 将 pipes 转数组,然后浅拷贝,保证后续修改 options 不会影响到 pipes
if (Array.isArray(pipes)) pipes = [...pipes];
else pipes = pipes ? [pipes] : []; // pipes 可能为 undefined

pipes.unshift(parseResponseBigint);
return axios.create(options);
const inst = axios.create({
...options,
// 由于 axios 自动解析 json 导致 bigint 精度丢失,要阻止它的 json 解析
// 如果用户已经给定 pipes,它会阻止 json 解析
// 如果用户没有给定 pipes,要用 `(x) => x` 阻止 json 解析
transformRequest: options?.transformRequest ?? ((x) => x),
transformResponse: options?.transformResponse ?? ((x) => x),
});
inst.interceptors.request.use(requestBigintInterceptor);
inst.interceptors.response.use(responseBigintInterceptor);
return inst;
}

/**
Expand Down