diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index a478c8b..09e1855 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -106,6 +106,7 @@ function sidebar() { { text: 'TRACE', link: '/method/TRACE' }, { text: 'CONNECT', link: '/method/CONNECT' }, ], + collapsed: false, }, { text: '基础', @@ -118,6 +119,7 @@ function sidebar() { { text: '错误处理', link: '/basics/error-handler' }, { text: '取消请求', link: '/basics/cancel' }, ], + collapsed: false, }, { text: '高级', @@ -128,6 +130,7 @@ function sidebar() { { text: '派生领域', link: '/advanced/fork' }, { text: '适配器', link: '/advanced/adapter' }, ], + collapsed: false, }, { diff --git a/docs/.vitepress/theme/styles/cover.css b/docs/.vitepress/theme/styles/cover.css index d3f6c58..c95bcb6 100644 --- a/docs/.vitepress/theme/styles/cover.css +++ b/docs/.vitepress/theme/styles/cover.css @@ -30,8 +30,8 @@ background-color: var(--vp-nav-bg-color); } -.VPSidebar .nav { - overflow-x: hidden; +.VPSidebar .group { + width: 100%; } .VPSidebarItem.level-0 .text { @@ -57,7 +57,7 @@ width: 4px; height: 4px; border-radius: 2px; - background-color: #07c160; + background-color: var(--vp-c-brand); position: absolute; top: 15px; left: 2px; @@ -73,6 +73,12 @@ overflow: auto; } +.VPMenu { + border: 1px solid var(--vp-c-gutter) !important; + border-radius: 6px !important; + box-shadow: none !important; +} + .VPHero .tagline { font-weight: normal; } @@ -110,7 +116,8 @@ .language-bash, .language-ts { - border: 1px solid var(--vp-c-gutter); + border-radius: none; + border: none; } .vp-code-group .tabs { @@ -135,6 +142,10 @@ box-shadow: none; } +.custom-block { + border-radius: 6px; +} + .dark .DocSearch-Modal { --docsearch-modal-background: var(--vp-c-bg-soft); } @@ -159,4 +170,10 @@ .VPFeatures .item { width: 100%; } + + .language-bash, + .language-ts { + border-radius: 6px; + border: 1px solid var(--vp-c-gutter); + } } diff --git a/docs/pages/basics/cancel.md b/docs/pages/basics/cancel.md index 21543d7..1fdf5d2 100644 --- a/docs/pages/basics/cancel.md +++ b/docs/pages/basics/cancel.md @@ -16,13 +16,12 @@ title: 取消请求 import axios from 'axios-miniprogram'; let cancel; -axios - .get('/test', { - cancelToken: new axios.CancelToken((c) => { - // executor 函数接收一个 cancel 函数作为参数 - cancel = c; - }), - }) +axios('https://api.com/test', { + cancelToken: new axios.CancelToken((c) => { + // executor 函数接收一个 cancel 函数作为参数 + cancel = c; + }), +}) .then((response) => { // 成功之后做些什么 }) @@ -42,10 +41,9 @@ cancel('request canceled'); import axios from 'axios-miniprogram'; const { cancel, token } = axios.CancelToken.source(); -axios - .get('https://api.com/test', { - cancelToken: token, - }) +axios('https://api.com/test', { + cancelToken: token, +}) .then((response) => { // 成功之后做些什么 }) @@ -65,10 +63,9 @@ cancel('request canceled'); import axios from 'axios-miniprogram'; const { cancel, token } = axios.CancelToken.source(); -axios - .get('https://api.com/test', { - cancelToken: token, - }) +axios('https://api.com/test', { + cancelToken: token, +}) .then((response) => { // 成功之后做些什么 }) diff --git a/docs/pages/basics/config.md b/docs/pages/basics/config.md index 05db93d..19c354d 100644 --- a/docs/pages/basics/config.md +++ b/docs/pages/basics/config.md @@ -85,7 +85,7 @@ axios({ console.log(status); }, - // 异常处理 + // 错误处理 errorHandler(error) { console.log(error); }, @@ -112,29 +112,28 @@ axios({ ```ts import axios from 'axios-miniprogram'; -axios - .request({ - // 开启 http2 - enableHttp2: false, +axios({ + // 开启 http2 + enableHttp2: false, - // 开启 quic - enableQuic: false, + // 开启 quic + enableQuic: false, - // 开启 cache - enableCache: false, + // 开启 cache + enableCache: false, - // 是否开启 HttpDNS 服务。如开启,需要同时填入 httpDNSServiceId 。 - enableHttpDNS: false, + // 是否开启 HttpDNS 服务。如开启,需要同时填入 httpDNSServiceId 。 + enableHttpDNS: false, - // HttpDNS 服务商 Id。 - httpDNSServiceId: '123', + // HttpDNS 服务商 Id。 + httpDNSServiceId: '123', - // 开启 transfer-encoding chunked。 - enableChunked: false, + // 开启 transfer-encoding chunked。 + enableChunked: false, - // wifi 下使用移动网络发送请求 - forceCellularNetwork: false, - }) + // wifi 下使用移动网络发送请求 + forceCellularNetwork: false, +}) .then((response) => { // 成功之后做些什么 }) @@ -154,14 +153,13 @@ axios ```ts import axios from 'axios-miniprogram'; -axios - .request({ - // 这是一个自定义配置 - user: '123', +axios({ + // 这是一个自定义配置 + user: '123', - // 这也是一个自定义配置 - showLoading: true, - }) + // 这也是一个自定义配置 + showLoading: true, +}) .then((response) => { // 成功之后做些什么 }) diff --git a/docs/pages/basics/error-handler.md b/docs/pages/basics/error-handler.md index db04d45..258484d 100644 --- a/docs/pages/basics/error-handler.md +++ b/docs/pages/basics/error-handler.md @@ -4,4 +4,115 @@ 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) => { + // 失败之后做些什么 + }); +``` diff --git a/src/adapter.ts b/src/adapter.ts index 427b814..c48ea78 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -314,7 +314,7 @@ export function createAdapter(platform: AxiosPlatform) { */ function cleanResponse(response: AnyObject, keys: string[]) { for (const key of keys) { - if (key in response) delete response[key]; + delete response[key]; } } diff --git a/src/core/Axios.ts b/src/core/Axios.ts index 0f868c3..5c5c81e 100644 --- a/src/core/Axios.ts +++ b/src/core/Axios.ts @@ -156,7 +156,7 @@ export interface AxiosRequestConfig */ transformResponse?: AxiosTransformer; /** - * 异常处理 + * 错误处理 */ errorHandler?: (error: unknown) => Promise | void; /** diff --git a/src/core/dispatchRequest.ts b/src/core/dispatchRequest.ts index 7d571c4..c9ec874 100644 --- a/src/core/dispatchRequest.ts +++ b/src/core/dispatchRequest.ts @@ -1,9 +1,9 @@ -import { isFunction, isString } from '../helpers/isTypes'; +import { isFunction, isPromise, isString } from '../helpers/isTypes'; import { assert } from '../helpers/error'; import { Cancel, isCancel, isCancelToken } from './cancel'; import { flattenHeaders } from './flattenHeaders'; import { AxiosTransformer, transformData } from './transformData'; -import { request } from './request'; +import { request, withDataRE } from './request'; import { AxiosRequestConfig, AxiosResponse } from './Axios'; import { transformURL } from './transformURL'; import { AxiosErrorResponse } from './createError'; @@ -27,7 +27,9 @@ export function dispatchRequest(config: AxiosRequestConfig) { config.url = transformURL(config); config.headers = flattenHeaders(config); - transformer(config, transformRequest); + if (withDataRE.test(config.method!)) { + transformer(config, transformRequest); + } function onSuccess(response: AxiosResponse) { throwIfCancellationRequested(config); @@ -43,10 +45,8 @@ export function dispatchRequest(config: AxiosRequestConfig) { if (isFunction(errorHandler)) { const promise = errorHandler(reason); - if (promise) { - return promise.then(() => { - throw reason; - }); + if (isPromise(promise)) { + return promise.then(() => Promise.reject(reason)); } } diff --git a/src/core/flattenHeaders.ts b/src/core/flattenHeaders.ts index 66ae3dd..a480236 100644 --- a/src/core/flattenHeaders.ts +++ b/src/core/flattenHeaders.ts @@ -4,9 +4,9 @@ import { AxiosRequestConfig, AxiosRequestHeaders } from './Axios'; export function flattenHeaders( config: AxiosRequestConfig, -): AxiosRequestHeaders | undefined { +): AxiosRequestHeaders { if (!isPlainObject(config.headers)) { - return config.headers; + return {}; } return { diff --git a/src/core/mergeConfig.ts b/src/core/mergeConfig.ts index 7958d4f..c5f447c 100644 --- a/src/core/mergeConfig.ts +++ b/src/core/mergeConfig.ts @@ -5,13 +5,13 @@ import { AxiosRequestConfig } from './Axios'; const fromConfig2Map: Record = { url: true, method: true, + data: true, upload: true, download: true, }; const deepMergeConfigMap: Record = { headers: true, params: true, - data: true, }; export function mergeConfig( diff --git a/src/core/request.ts b/src/core/request.ts index 2ba3893..3cacd8a 100644 --- a/src/core/request.ts +++ b/src/core/request.ts @@ -15,6 +15,7 @@ import { import { isCancelToken } from './cancel'; import { AxiosErrorResponse, createError } from './createError'; import { generateType } from './generateType'; +import AxiosDomain from './AxiosDomain'; function tryToggleProgressUpdate( adapterConfig: AxiosAdapterRequestConfig, @@ -37,10 +38,19 @@ function tryToggleProgressUpdate( } } +/** + * 可以携带 data 的请求方法 + */ +export const withDataRE = new RegExp(`^${AxiosDomain.asd.join('|')}$`, 'i'); + export function request(config: AxiosRequestConfig) { return new Promise((resolve, reject) => { const { adapter, url, method, cancelToken } = config; + if (!withDataRE.test(method!)) { + delete config.data; + } + const adapterConfig: AxiosAdapterRequestConfig = { ...config, url: url!, diff --git a/src/helpers/isTypes.ts b/src/helpers/isTypes.ts index 5c2bf71..28e7743 100644 --- a/src/helpers/isTypes.ts +++ b/src/helpers/isTypes.ts @@ -34,3 +34,10 @@ export function isDate(date: any): date is Date { export function isFunction(value: any): value is T { return typeof value === 'function'; } + +export function isPromise(value: any): value is Promise { + return ( + _toString.call(value) === '[object Promise]' || + (isPlainObject(value) && isFunction(value.then)) + ); +} diff --git a/test/core/Axios.test.ts b/test/core/Axios.test.ts index 669d22d..dfe2896 100644 --- a/test/core/Axios.test.ts +++ b/test/core/Axios.test.ts @@ -144,18 +144,18 @@ describe('src/core/Axios.ts', () => { const axios = new Axios(); const cb1 = vi.fn((v) => { - expect(v.data.index).toBe(2); - v.data.index = 3; + expect(v.params.index).toBe(2); + v.params.index = 3; return v; }); const cb2 = vi.fn((v) => { - expect(v.data.index).toBe(1); - v.data.index = 2; + expect(v.params.index).toBe(1); + v.params.index = 2; return v; }); const cb3 = vi.fn((v) => { - expect(v.data.index).toBe(0); - v.data.index = 1; + expect(v.params.index).toBe(0); + v.params.index = 1; return v; }); @@ -165,9 +165,9 @@ describe('src/core/Axios.ts', () => { axios.request('/test', { adapter: (config) => { - expect(config.data!.index).toBe(3); + expect(config.params!.index).toBe(3); }, - data: { + params: { index: 0, }, }); diff --git a/test/core/dispatchRequest.test.ts b/test/core/dispatchRequest.test.ts index 676cfd7..499c947 100644 --- a/test/core/dispatchRequest.test.ts +++ b/test/core/dispatchRequest.test.ts @@ -5,6 +5,7 @@ import { mockAdapterError, mockAdapterFail, } from 'scripts/test.utils'; +import Axios from '@/core/Axios'; import { dispatchRequest } from '@/core/dispatchRequest'; import axios from '@/axios'; import _defaults from '@/defaults'; @@ -50,8 +51,7 @@ describe('src/core/dispatchRequest.ts', () => { { "config": { "adapter": [Function], - "data": undefined, - "headers": undefined, + "headers": {}, "method": "get", "url": "", }, @@ -59,8 +59,7 @@ describe('src/core/dispatchRequest.ts', () => { "response": { "config": { "adapter": [Function], - "data": undefined, - "headers": undefined, + "headers": {}, "method": "get", "url": "", }, @@ -136,9 +135,11 @@ describe('src/core/dispatchRequest.ts', () => { transformRequest: () => ({ id: 1 }), }; - dispatchRequest(c); - - expect(c.data).toEqual({ id: 1 }); + Axios.asd.forEach((k) => { + const s = { ...c, method: k }; + dispatchRequest(s); + expect(s.data).toEqual({ id: 1 }); + }); }); test('应该支持转换响应数据', async () => { @@ -153,24 +154,20 @@ describe('src/core/dispatchRequest.ts', () => { expect(r.data).toEqual({ result: 1 }); }); - test('应该支持自定义异常处理器', async () => { + test('应该支持错误处理', async () => { const e1 = vi.fn(); const e2 = vi.fn(); const c1 = { ...defaults, adapter: mockAdapterError(), url: 'test', - errorHandler: async (err: unknown) => { - e1(err); - }, + errorHandler: e1, }; const c2 = { ...defaults, adapter: mockAdapterFail(), url: 'test', - errorHandler: async (err: unknown) => { - e2(err); - }, + errorHandler: e2, }; try { @@ -190,20 +187,24 @@ describe('src/core/dispatchRequest.ts', () => { } }); - test('应该支持异步的自定义异常处理器', async () => { + test('应该支持异步错误处理', async () => { const e1 = vi.fn(); const e2 = vi.fn(); const c1 = { ...defaults, adapter: mockAdapterError(), url: 'test', - errorHandler: e1, + errorHandler: async (err: unknown) => { + e1(err); + }, }; const c2 = { ...defaults, adapter: mockAdapterFail(), url: 'test', - errorHandler: e2, + errorHandler: async (err: unknown) => { + e2(err); + }, }; try { diff --git a/test/core/flattenHeaders.test.ts b/test/core/flattenHeaders.test.ts index bc7f9df..5757e7b 100644 --- a/test/core/flattenHeaders.test.ts +++ b/test/core/flattenHeaders.test.ts @@ -9,7 +9,7 @@ describe('src/core/flattenHeaders.ts', () => { ) as unknown as Record<(typeof keys)[number], AnyObject>; test('应该支持空配置', () => { - expect(flattenHeaders({})).toBeUndefined(); + expect(flattenHeaders({})).toEqual({}); }); test('应该支持自定义 headers', () => { diff --git a/test/core/mergeConfig.test.ts b/test/core/mergeConfig.test.ts index 866c49a..ccf4c8b 100644 --- a/test/core/mergeConfig.test.ts +++ b/test/core/mergeConfig.test.ts @@ -18,12 +18,18 @@ describe('src/core/mergeConfig.ts', () => { const c1 = { url: 'a', method: 'get' as const, + data: { + v1: '1', + }, upload: true, download: true, }; const c2 = { url: 'b', method: 'post' as const, + data: { + v1: '2', + }, upload: false, download: false, }; @@ -71,7 +77,6 @@ describe('src/core/mergeConfig.ts', () => { v1: 1, }, params: o1, - data: o1, }; const c2 = { headers: { @@ -81,7 +86,6 @@ describe('src/core/mergeConfig.ts', () => { v2: 2, }, params: o2, - data: o2, }; const mc = { headers: { @@ -93,7 +97,6 @@ describe('src/core/mergeConfig.ts', () => { v2: 2, }, params: o3, - data: o3, }; expect(mergeConfig(c1, {})).toEqual(c1); @@ -117,12 +120,10 @@ describe('src/core/mergeConfig.ts', () => { const c1 = { headers: 1, params: '1', - data: [], }; const c2 = { headers: () => null, params: null, - data: new Date(), }; expect(mergeConfig(c1 as any, c2 as any)).toEqual({}); diff --git a/test/core/request.test.ts b/test/core/request.test.ts index 42a4fe6..4dbebef 100644 --- a/test/core/request.test.ts +++ b/test/core/request.test.ts @@ -174,6 +174,20 @@ describe('src/core/request.ts', () => { 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('应该发送不同类型的请求', () => { request({ adapter: ({ type }) => { diff --git a/test/helpers/isTypes.test.ts b/test/helpers/isTypes.test.ts index f8a10bb..3181d4f 100644 --- a/test/helpers/isTypes.test.ts +++ b/test/helpers/isTypes.test.ts @@ -8,6 +8,7 @@ import { isNull, isUndefined, isString, + isPromise, } from '@/helpers/isTypes'; describe('src/helpers/isTypes.ts', () => { @@ -60,4 +61,23 @@ describe('src/helpers/isTypes.ts', () => { 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(); + }); });