feat: 仅 post/put/patch 方法允许设置请求数据

pull/41/head
zjx0905 2023-04-16 13:39:10 +08:00
parent ab6fd668c0
commit 2c3ff567c1
18 changed files with 267 additions and 88 deletions

View File

@ -106,6 +106,7 @@ function sidebar() {
{ text: 'TRACE', link: '/method/TRACE' }, { text: 'TRACE', link: '/method/TRACE' },
{ text: 'CONNECT', link: '/method/CONNECT' }, { text: 'CONNECT', link: '/method/CONNECT' },
], ],
collapsed: false,
}, },
{ {
text: '基础', text: '基础',
@ -118,6 +119,7 @@ function sidebar() {
{ text: '错误处理', link: '/basics/error-handler' }, { text: '错误处理', link: '/basics/error-handler' },
{ text: '取消请求', link: '/basics/cancel' }, { text: '取消请求', link: '/basics/cancel' },
], ],
collapsed: false,
}, },
{ {
text: '高级', text: '高级',
@ -128,6 +130,7 @@ function sidebar() {
{ text: '派生领域', link: '/advanced/fork' }, { text: '派生领域', link: '/advanced/fork' },
{ text: '适配器', link: '/advanced/adapter' }, { text: '适配器', link: '/advanced/adapter' },
], ],
collapsed: false,
}, },
{ {

View File

@ -30,8 +30,8 @@
background-color: var(--vp-nav-bg-color); background-color: var(--vp-nav-bg-color);
} }
.VPSidebar .nav { .VPSidebar .group {
overflow-x: hidden; width: 100%;
} }
.VPSidebarItem.level-0 .text { .VPSidebarItem.level-0 .text {
@ -57,7 +57,7 @@
width: 4px; width: 4px;
height: 4px; height: 4px;
border-radius: 2px; border-radius: 2px;
background-color: #07c160; background-color: var(--vp-c-brand);
position: absolute; position: absolute;
top: 15px; top: 15px;
left: 2px; left: 2px;
@ -73,6 +73,12 @@
overflow: auto; overflow: auto;
} }
.VPMenu {
border: 1px solid var(--vp-c-gutter) !important;
border-radius: 6px !important;
box-shadow: none !important;
}
.VPHero .tagline { .VPHero .tagline {
font-weight: normal; font-weight: normal;
} }
@ -110,7 +116,8 @@
.language-bash, .language-bash,
.language-ts { .language-ts {
border: 1px solid var(--vp-c-gutter); border-radius: none;
border: none;
} }
.vp-code-group .tabs { .vp-code-group .tabs {
@ -135,6 +142,10 @@
box-shadow: none; box-shadow: none;
} }
.custom-block {
border-radius: 6px;
}
.dark .DocSearch-Modal { .dark .DocSearch-Modal {
--docsearch-modal-background: var(--vp-c-bg-soft); --docsearch-modal-background: var(--vp-c-bg-soft);
} }
@ -159,4 +170,10 @@
.VPFeatures .item { .VPFeatures .item {
width: 100%; width: 100%;
} }
.language-bash,
.language-ts {
border-radius: 6px;
border: 1px solid var(--vp-c-gutter);
}
} }

View File

@ -16,13 +16,12 @@ title: 取消请求
import axios from 'axios-miniprogram'; import axios from 'axios-miniprogram';
let cancel; let cancel;
axios axios('https://api.com/test', {
.get('/test', { cancelToken: new axios.CancelToken((c) => {
cancelToken: new axios.CancelToken((c) => { // executor 函数接收一个 cancel 函数作为参数
// executor 函数接收一个 cancel 函数作为参数 cancel = c;
cancel = c; }),
}), })
})
.then((response) => { .then((response) => {
// 成功之后做些什么 // 成功之后做些什么
}) })
@ -42,10 +41,9 @@ cancel('request canceled');
import axios from 'axios-miniprogram'; import axios from 'axios-miniprogram';
const { cancel, token } = axios.CancelToken.source(); const { cancel, token } = axios.CancelToken.source();
axios axios('https://api.com/test', {
.get('https://api.com/test', { cancelToken: token,
cancelToken: token, })
})
.then((response) => { .then((response) => {
// 成功之后做些什么 // 成功之后做些什么
}) })
@ -65,10 +63,9 @@ cancel('request canceled');
import axios from 'axios-miniprogram'; import axios from 'axios-miniprogram';
const { cancel, token } = axios.CancelToken.source(); const { cancel, token } = axios.CancelToken.source();
axios axios('https://api.com/test', {
.get('https://api.com/test', { cancelToken: token,
cancelToken: token, })
})
.then((response) => { .then((response) => {
// 成功之后做些什么 // 成功之后做些什么
}) })

View File

@ -85,7 +85,7 @@ axios({
console.log(status); console.log(status);
}, },
// 异常处理 // 错误处理
errorHandler(error) { errorHandler(error) {
console.log(error); console.log(error);
}, },
@ -112,29 +112,28 @@ axios({
```ts ```ts
import axios from 'axios-miniprogram'; import axios from 'axios-miniprogram';
axios axios({
.request({ // 开启 http2
// 开启 http2 enableHttp2: false,
enableHttp2: false,
// 开启 quic // 开启 quic
enableQuic: false, enableQuic: false,
// 开启 cache // 开启 cache
enableCache: false, enableCache: false,
// 是否开启 HttpDNS 服务。如开启,需要同时填入 httpDNSServiceId 。 // 是否开启 HttpDNS 服务。如开启,需要同时填入 httpDNSServiceId 。
enableHttpDNS: false, enableHttpDNS: false,
// HttpDNS 服务商 Id。 // HttpDNS 服务商 Id。
httpDNSServiceId: '123', httpDNSServiceId: '123',
// 开启 transfer-encoding chunked。 // 开启 transfer-encoding chunked。
enableChunked: false, enableChunked: false,
// wifi 下使用移动网络发送请求 // wifi 下使用移动网络发送请求
forceCellularNetwork: false, forceCellularNetwork: false,
}) })
.then((response) => { .then((response) => {
// 成功之后做些什么 // 成功之后做些什么
}) })
@ -154,14 +153,13 @@ axios
```ts ```ts
import axios from 'axios-miniprogram'; import axios from 'axios-miniprogram';
axios axios({
.request({ // 这是一个自定义配置
// 这是一个自定义配置 user: '123',
user: '123',
// 这也是一个自定义配置 // 这也是一个自定义配置
showLoading: true, showLoading: true,
}) })
.then((response) => { .then((response) => {
// 成功之后做些什么 // 成功之后做些什么
}) })

View File

@ -4,4 +4,115 @@ title: 错误处理
# {{ $frontmatter.title }} # {{ $frontmatter.title }}
待续... ::: tip {{ $frontmatter.title }}
当请求失败时,可以对错误进行处理。
:::
## 使用 `validateStatus` 抛出错误
可以使用 `validateStatus` 自定义抛出错误的 HTTP code。
```ts
import axios from 'axios-miniprogram';
axios('https://api.com/test', {
validateStatus(status) {
// status 小于 200 大于 299 会抛出错误
return status >= 200 && status < 300;
},
})
.then((response) => {
// 成功之后做些什么
})
.catch((error) => {
// 失败之后做些什么
});
```
## 在 `catch` 中处理错误
可以处理不同类型的错误。
```ts
import axios from 'axios-miniprogram';
axios('https://api.com/test')
.then((response) => {
// 成功之后做些什么
})
.catch((error) => {
if (axios.isAxiosError(error)) {
// 响应错误
const {
// 错误消息
message,
// 请求配置
config,
// 请求任务,也就是请求函数返回的结果
request,
// 响应体
response,
} = error;
if (response.isFail) {
// 平台或适配器错误
} else {
// 使用 `validateStatus` 自定义抛出的错误
}
} else if (axios.isCancel(error)) {
// 取消请求
} else {
// 其他错误
}
});
```
## 使用 `errorHandler` 处理错误。
可以使用 `errorHandler` 处理不同类型的错误。
```ts
import axios from 'axios-miniprogram';
axios('https://api.com/test', {
errorHandler(error) {
if (axios.isAxiosError(error)) {
// 响应错误
const {
// 错误消息
message,
// 请求配置
config,
// 请求任务,也就是请求函数返回的结果
request,
// 响应体
response,
} = error;
if (response.isFail) {
// 平台或适配器错误
} else {
// 使用 `validateStatus` 自定义抛出的错误
}
} else if (axios.isCancel(error)) {
// 取消请求
} else {
// 其他错误
}
},
})
.then((response) => {
// 成功之后做些什么
})
.catch((error) => {
// 失败之后做些什么
});
```

View File

@ -314,7 +314,7 @@ export function createAdapter(platform: AxiosPlatform) {
*/ */
function cleanResponse(response: AnyObject, keys: string[]) { function cleanResponse(response: AnyObject, keys: string[]) {
for (const key of keys) { for (const key of keys) {
if (key in response) delete response[key]; delete response[key];
} }
} }

View File

@ -156,7 +156,7 @@ export interface AxiosRequestConfig
*/ */
transformResponse?: AxiosTransformer<AxiosResponseData>; transformResponse?: AxiosTransformer<AxiosResponseData>;
/** /**
* *
*/ */
errorHandler?: (error: unknown) => Promise<void> | void; errorHandler?: (error: unknown) => Promise<void> | void;
/** /**

View File

@ -1,9 +1,9 @@
import { isFunction, isString } from '../helpers/isTypes'; import { isFunction, isPromise, isString } from '../helpers/isTypes';
import { assert } from '../helpers/error'; import { assert } from '../helpers/error';
import { Cancel, isCancel, isCancelToken } from './cancel'; import { Cancel, isCancel, isCancelToken } from './cancel';
import { flattenHeaders } from './flattenHeaders'; import { flattenHeaders } from './flattenHeaders';
import { AxiosTransformer, transformData } from './transformData'; import { AxiosTransformer, transformData } from './transformData';
import { request } from './request'; import { request, withDataRE } from './request';
import { AxiosRequestConfig, AxiosResponse } from './Axios'; import { AxiosRequestConfig, AxiosResponse } from './Axios';
import { transformURL } from './transformURL'; import { transformURL } from './transformURL';
import { AxiosErrorResponse } from './createError'; import { AxiosErrorResponse } from './createError';
@ -27,7 +27,9 @@ export function dispatchRequest(config: AxiosRequestConfig) {
config.url = transformURL(config); config.url = transformURL(config);
config.headers = flattenHeaders(config); config.headers = flattenHeaders(config);
transformer(config, transformRequest); if (withDataRE.test(config.method!)) {
transformer(config, transformRequest);
}
function onSuccess(response: AxiosResponse) { function onSuccess(response: AxiosResponse) {
throwIfCancellationRequested(config); throwIfCancellationRequested(config);
@ -43,10 +45,8 @@ export function dispatchRequest(config: AxiosRequestConfig) {
if (isFunction(errorHandler)) { if (isFunction(errorHandler)) {
const promise = errorHandler(reason); const promise = errorHandler(reason);
if (promise) { if (isPromise(promise)) {
return promise.then(() => { return promise.then(() => Promise.reject(reason));
throw reason;
});
} }
} }

View File

@ -4,9 +4,9 @@ import { AxiosRequestConfig, AxiosRequestHeaders } from './Axios';
export function flattenHeaders( export function flattenHeaders(
config: AxiosRequestConfig, config: AxiosRequestConfig,
): AxiosRequestHeaders | undefined { ): AxiosRequestHeaders {
if (!isPlainObject(config.headers)) { if (!isPlainObject(config.headers)) {
return config.headers; return {};
} }
return { return {

View File

@ -5,13 +5,13 @@ import { AxiosRequestConfig } from './Axios';
const fromConfig2Map: Record<string, boolean> = { const fromConfig2Map: Record<string, boolean> = {
url: true, url: true,
method: true, method: true,
data: true,
upload: true, upload: true,
download: true, download: true,
}; };
const deepMergeConfigMap: Record<string, boolean> = { const deepMergeConfigMap: Record<string, boolean> = {
headers: true, headers: true,
params: true, params: true,
data: true,
}; };
export function mergeConfig( export function mergeConfig(

View File

@ -15,6 +15,7 @@ import {
import { isCancelToken } from './cancel'; import { isCancelToken } from './cancel';
import { AxiosErrorResponse, createError } from './createError'; import { AxiosErrorResponse, createError } from './createError';
import { generateType } from './generateType'; import { generateType } from './generateType';
import AxiosDomain from './AxiosDomain';
function tryToggleProgressUpdate( function tryToggleProgressUpdate(
adapterConfig: AxiosAdapterRequestConfig, adapterConfig: AxiosAdapterRequestConfig,
@ -37,10 +38,19 @@ function tryToggleProgressUpdate(
} }
} }
/**
* data
*/
export const withDataRE = new RegExp(`^${AxiosDomain.asd.join('|')}$`, 'i');
export function request(config: AxiosRequestConfig) { export function request(config: AxiosRequestConfig) {
return new Promise<AxiosResponse>((resolve, reject) => { return new Promise<AxiosResponse>((resolve, reject) => {
const { adapter, url, method, cancelToken } = config; const { adapter, url, method, cancelToken } = config;
if (!withDataRE.test(method!)) {
delete config.data;
}
const adapterConfig: AxiosAdapterRequestConfig = { const adapterConfig: AxiosAdapterRequestConfig = {
...config, ...config,
url: url!, url: url!,

View File

@ -34,3 +34,10 @@ export function isDate(date: any): date is Date {
export function isFunction<T extends Function>(value: any): value is T { export function isFunction<T extends Function>(value: any): value is T {
return typeof value === 'function'; return typeof value === 'function';
} }
export function isPromise<T = unknown>(value: any): value is Promise<T> {
return (
_toString.call(value) === '[object Promise]' ||
(isPlainObject(value) && isFunction(value.then))
);
}

View File

@ -144,18 +144,18 @@ describe('src/core/Axios.ts', () => {
const axios = new Axios(); const axios = new Axios();
const cb1 = vi.fn((v) => { const cb1 = vi.fn((v) => {
expect(v.data.index).toBe(2); expect(v.params.index).toBe(2);
v.data.index = 3; v.params.index = 3;
return v; return v;
}); });
const cb2 = vi.fn((v) => { const cb2 = vi.fn((v) => {
expect(v.data.index).toBe(1); expect(v.params.index).toBe(1);
v.data.index = 2; v.params.index = 2;
return v; return v;
}); });
const cb3 = vi.fn((v) => { const cb3 = vi.fn((v) => {
expect(v.data.index).toBe(0); expect(v.params.index).toBe(0);
v.data.index = 1; v.params.index = 1;
return v; return v;
}); });
@ -165,9 +165,9 @@ describe('src/core/Axios.ts', () => {
axios.request('/test', { axios.request('/test', {
adapter: (config) => { adapter: (config) => {
expect(config.data!.index).toBe(3); expect(config.params!.index).toBe(3);
}, },
data: { params: {
index: 0, index: 0,
}, },
}); });

View File

@ -5,6 +5,7 @@ import {
mockAdapterError, mockAdapterError,
mockAdapterFail, mockAdapterFail,
} from 'scripts/test.utils'; } from 'scripts/test.utils';
import Axios from '@/core/Axios';
import { dispatchRequest } from '@/core/dispatchRequest'; import { dispatchRequest } from '@/core/dispatchRequest';
import axios from '@/axios'; import axios from '@/axios';
import _defaults from '@/defaults'; import _defaults from '@/defaults';
@ -50,8 +51,7 @@ describe('src/core/dispatchRequest.ts', () => {
{ {
"config": { "config": {
"adapter": [Function], "adapter": [Function],
"data": undefined, "headers": {},
"headers": undefined,
"method": "get", "method": "get",
"url": "", "url": "",
}, },
@ -59,8 +59,7 @@ describe('src/core/dispatchRequest.ts', () => {
"response": { "response": {
"config": { "config": {
"adapter": [Function], "adapter": [Function],
"data": undefined, "headers": {},
"headers": undefined,
"method": "get", "method": "get",
"url": "", "url": "",
}, },
@ -136,9 +135,11 @@ describe('src/core/dispatchRequest.ts', () => {
transformRequest: () => ({ id: 1 }), transformRequest: () => ({ id: 1 }),
}; };
dispatchRequest(c); Axios.asd.forEach((k) => {
const s = { ...c, method: k };
expect(c.data).toEqual({ id: 1 }); dispatchRequest(s);
expect(s.data).toEqual({ id: 1 });
});
}); });
test('应该支持转换响应数据', async () => { test('应该支持转换响应数据', async () => {
@ -153,24 +154,20 @@ describe('src/core/dispatchRequest.ts', () => {
expect(r.data).toEqual({ result: 1 }); expect(r.data).toEqual({ result: 1 });
}); });
test('应该支持自定义异常处理器', async () => { test('应该支持错误处理', async () => {
const e1 = vi.fn(); const e1 = vi.fn();
const e2 = vi.fn(); const e2 = vi.fn();
const c1 = { const c1 = {
...defaults, ...defaults,
adapter: mockAdapterError(), adapter: mockAdapterError(),
url: 'test', url: 'test',
errorHandler: async (err: unknown) => { errorHandler: e1,
e1(err);
},
}; };
const c2 = { const c2 = {
...defaults, ...defaults,
adapter: mockAdapterFail(), adapter: mockAdapterFail(),
url: 'test', url: 'test',
errorHandler: async (err: unknown) => { errorHandler: e2,
e2(err);
},
}; };
try { try {
@ -190,20 +187,24 @@ describe('src/core/dispatchRequest.ts', () => {
} }
}); });
test('应该支持异步的自定义异常处理器', async () => { test('应该支持异步错误处理', async () => {
const e1 = vi.fn(); const e1 = vi.fn();
const e2 = vi.fn(); const e2 = vi.fn();
const c1 = { const c1 = {
...defaults, ...defaults,
adapter: mockAdapterError(), adapter: mockAdapterError(),
url: 'test', url: 'test',
errorHandler: e1, errorHandler: async (err: unknown) => {
e1(err);
},
}; };
const c2 = { const c2 = {
...defaults, ...defaults,
adapter: mockAdapterFail(), adapter: mockAdapterFail(),
url: 'test', url: 'test',
errorHandler: e2, errorHandler: async (err: unknown) => {
e2(err);
},
}; };
try { try {

View File

@ -9,7 +9,7 @@ describe('src/core/flattenHeaders.ts', () => {
) as unknown as Record<(typeof keys)[number], AnyObject>; ) as unknown as Record<(typeof keys)[number], AnyObject>;
test('应该支持空配置', () => { test('应该支持空配置', () => {
expect(flattenHeaders({})).toBeUndefined(); expect(flattenHeaders({})).toEqual({});
}); });
test('应该支持自定义 headers', () => { test('应该支持自定义 headers', () => {

View File

@ -18,12 +18,18 @@ describe('src/core/mergeConfig.ts', () => {
const c1 = { const c1 = {
url: 'a', url: 'a',
method: 'get' as const, method: 'get' as const,
data: {
v1: '1',
},
upload: true, upload: true,
download: true, download: true,
}; };
const c2 = { const c2 = {
url: 'b', url: 'b',
method: 'post' as const, method: 'post' as const,
data: {
v1: '2',
},
upload: false, upload: false,
download: false, download: false,
}; };
@ -71,7 +77,6 @@ describe('src/core/mergeConfig.ts', () => {
v1: 1, v1: 1,
}, },
params: o1, params: o1,
data: o1,
}; };
const c2 = { const c2 = {
headers: { headers: {
@ -81,7 +86,6 @@ describe('src/core/mergeConfig.ts', () => {
v2: 2, v2: 2,
}, },
params: o2, params: o2,
data: o2,
}; };
const mc = { const mc = {
headers: { headers: {
@ -93,7 +97,6 @@ describe('src/core/mergeConfig.ts', () => {
v2: 2, v2: 2,
}, },
params: o3, params: o3,
data: o3,
}; };
expect(mergeConfig(c1, {})).toEqual(c1); expect(mergeConfig(c1, {})).toEqual(c1);
@ -117,12 +120,10 @@ describe('src/core/mergeConfig.ts', () => {
const c1 = { const c1 = {
headers: 1, headers: 1,
params: '1', params: '1',
data: [],
}; };
const c2 = { const c2 = {
headers: () => null, headers: () => null,
params: null, params: null,
data: new Date(),
}; };
expect(mergeConfig(c1 as any, c2 as any)).toEqual({}); expect(mergeConfig(c1 as any, c2 as any)).toEqual({});

View File

@ -174,6 +174,20 @@ describe('src/core/request.ts', () => {
expect(axios.isCancel(cb.mock.calls[0][0])).toBeTruthy(); expect(axios.isCancel(cb.mock.calls[0][0])).toBeTruthy();
}); });
test('应该删除请求数据', () => {
const c = {
adapter: mockAdapter(),
url: 'test',
data: {},
};
[...Axios.as, ...Axios.asp].forEach((k) => {
const s = { ...c, method: k };
request(s);
expect(s.data).toBeUndefined();
});
});
test('应该发送不同类型的请求', () => { test('应该发送不同类型的请求', () => {
request({ request({
adapter: ({ type }) => { adapter: ({ type }) => {

View File

@ -8,6 +8,7 @@ import {
isNull, isNull,
isUndefined, isUndefined,
isString, isString,
isPromise,
} from '@/helpers/isTypes'; } from '@/helpers/isTypes';
describe('src/helpers/isTypes.ts', () => { describe('src/helpers/isTypes.ts', () => {
@ -60,4 +61,23 @@ describe('src/helpers/isTypes.ts', () => {
expect(isString('')).toBeTruthy(); expect(isString('')).toBeTruthy();
expect(isString(``)).toBeTruthy(); expect(isString(``)).toBeTruthy();
}); });
test('应该能判断是 Promise', () => {
expect(isPromise({})).toBeFalsy();
expect(isPromise(Promise.resolve())).toBeTruthy();
expect(
isPromise(
new Promise(function () {
return;
}),
),
).toBeTruthy();
expect(
isPromise({
then() {
return;
},
}),
).toBeTruthy();
});
}); });