diff --git a/docs/.vitepress/theme/styles/cover.css b/docs/.vitepress/theme/styles/cover.css index 2d29e7e..4dd0ca5 100644 --- a/docs/.vitepress/theme/styles/cover.css +++ b/docs/.vitepress/theme/styles/cover.css @@ -172,7 +172,7 @@ .custom-block { border-radius: 6px; - background-color: var(--vp-custom-block-tip-bg); + background-color: var(--vp-custom-block-tip-bg) !important; } .dark .DocSearch-Modal { diff --git a/docs/pages/advanced/response-interceptor.md b/docs/pages/advanced/response-interceptor.md index c097d3a..3a33fb7 100644 --- a/docs/pages/advanced/response-interceptor.md +++ b/docs/pages/advanced/response-interceptor.md @@ -88,7 +88,7 @@ axios.interceptors.response.eject(ejectId); ```ts import axios from 'axios-miniprogram'; -axios.interceptors.request.use( +axios.interceptors.response.use( function (response) { // 在 then 之前做些什么 return response; @@ -99,7 +99,7 @@ axios.interceptors.request.use( }, ); -axios.interceptors.request.use( +axios.interceptors.response.use( function (response) { // 在 then 之前做些什么 return response; @@ -111,5 +111,5 @@ axios.interceptors.request.use( ); // 移除所有响应拦截器 -axios.interceptors.request.clear(); +axios.interceptors.response.clear(); ``` diff --git a/src/core/Axios.ts b/src/core/Axios.ts index 7b2f754..76bad0d 100644 --- a/src/core/Axios.ts +++ b/src/core/Axios.ts @@ -1,7 +1,7 @@ import { buildURL } from '../helpers/buildURL'; import { isAbsoluteURL } from '../helpers/isAbsoluteURL'; import { combineURL } from '../helpers/combineURL'; -import { isString } from '../helpers/isTypes'; +import { isFunction, isPromise, isString } from '../helpers/isTypes'; import { AxiosAdapter, AxiosAdapterRequestMethod, @@ -274,6 +274,20 @@ export default class Axios extends AxiosDomain { ); } + // 错误处理 + next = next.catch((reason) => { + const { errorHandler } = config; + + if (isFunction(errorHandler)) { + const promise = errorHandler(reason); + if (isPromise(promise)) { + return promise.then(() => Promise.reject(reason)); + } + } + + return Promise.reject(reason); + }); + return next as Promise; } } diff --git a/src/core/dispatchRequest.ts b/src/core/dispatchRequest.ts index c9ec874..5774961 100644 --- a/src/core/dispatchRequest.ts +++ b/src/core/dispatchRequest.ts @@ -1,4 +1,4 @@ -import { isFunction, isPromise, isString } from '../helpers/isTypes'; +import { isFunction, isString } from '../helpers/isTypes'; import { assert } from '../helpers/error'; import { Cancel, isCancel, isCancelToken } from './cancel'; import { flattenHeaders } from './flattenHeaders'; @@ -22,47 +22,35 @@ export function dispatchRequest(config: AxiosRequestConfig) { assert(isString(config.url), 'url 不是一个 string'); assert(isString(config.method), 'method 不是一个 string'); - const { errorHandler, transformRequest, transformResponse } = config; - config.url = transformURL(config); config.headers = flattenHeaders(config); if (withDataRE.test(config.method!)) { - transformer(config, transformRequest); + transformer(config, config.transformRequest); } function onSuccess(response: AxiosResponse) { throwIfCancellationRequested(config); - transformer(response, transformResponse); + transformer(response, config.transformResponse); + return response; } function onError(reason: Cancel | AxiosErrorResponse) { if (!isCancel(reason)) { throwIfCancellationRequested(config); - transformer(reason.response, transformResponse); - } - - if (isFunction(errorHandler)) { - const promise = errorHandler(reason); - if (isPromise(promise)) { - return promise.then(() => Promise.reject(reason)); - } + transformer(reason.response, config.transformResponse); } return Promise.reject(reason); } function transformer( - targetObject: { data?: TData; headers?: AnyObject }, - transformer?: AxiosTransformer, + target: { data?: TData; headers?: AnyObject }, + fn?: AxiosTransformer, ) { - targetObject.data = transformData( - targetObject.data, - targetObject.headers, - transformer, - ); + target.data = transformData(target.data, target.headers, fn); } - return request(config).then(onSuccess).catch(onError); + return request(config).then(onSuccess, onError); } diff --git a/test/core/Axios.test.ts b/test/core/Axios.test.ts index 14468f0..13f6647 100644 --- a/test/core/Axios.test.ts +++ b/test/core/Axios.test.ts @@ -1,13 +1,18 @@ import { describe, test, expect, vi } from 'vitest'; -import { mockAdapter } from 'scripts/test.utils'; -import Axios from '@/core/Axios'; +import { + mockAdapter, + mockAdapterError, + mockAdapterFail, +} from 'scripts/test.utils'; import AxiosDomain from '@/core/AxiosDomain'; +import Axios from '@/core/Axios'; +import axios from '@/axios'; describe('src/core/Axios.ts', () => { const data = { result: null, }; - const axios = new Axios({ + const axiosObj = new Axios({ baseURL: 'http://api.com', }); @@ -26,14 +31,14 @@ describe('src/core/Axios.ts', () => { baseURL: 'http://api.com', }; - expect(axios.defaults).toEqual(c); - expect(axios.interceptors).toBeTypeOf('object'); - expect(axios.request).toBeTypeOf('function'); - expect(axios.getUri).toBeTypeOf('function'); - expect(axios.fork).toBeTypeOf('function'); + expect(axiosObj.defaults).toEqual(c); + expect(axiosObj.interceptors).toBeTypeOf('object'); + expect(axiosObj.request).toBeTypeOf('function'); + expect(axiosObj.getUri).toBeTypeOf('function'); + expect(axiosObj.fork).toBeTypeOf('function'); [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => { - expect(axios[k]).toBeTypeOf('function'); + expect(axiosObj[k]).toBeTypeOf('function'); }); }); @@ -48,7 +53,7 @@ describe('src/core/Axios.ts', () => { }; Axios.as.forEach((a) => { - axios[a]('test', c).then((res) => { + axiosObj[a]('test', c).then((res) => { expect(res.data).toEqual(data); }); }); @@ -76,10 +81,10 @@ describe('src/core/Axios.ts', () => { }; Axios.asp.forEach((a) => { - axios[a]('test', p, c1).then((res) => { + axiosObj[a]('test', p, c1).then((res) => { expect(res.data).toEqual(data); }); - axios[a]('test/:id', { ...p }, c2).then((res) => { + axiosObj[a]('test/:id', { ...p }, c2).then((res) => { expect(res.data).toEqual(data); }); }); @@ -107,33 +112,99 @@ describe('src/core/Axios.ts', () => { }; Axios.asd.forEach((a) => { - axios[a]('test', d, c1).then((res) => { + axiosObj[a]('test', d, c1).then((res) => { expect(res.data).toEqual(data); }); - axios[a]('test/:id', d, c2).then((res) => { + axiosObj[a]('test/:id', d, c2).then((res) => { expect(res.data).toEqual(data); }); }); }); + test('应该支持错误处理', async () => { + const e1 = vi.fn(); + const e2 = vi.fn(); + const c1 = { + adapter: mockAdapterError(), + url: 'test', + errorHandler: e1, + }; + const c2 = { + adapter: mockAdapterFail(), + url: 'test', + errorHandler: e2, + }; + + try { + await axiosObj.request(c1); + } catch (err) { + expect(e1).toBeCalled(); + expect(e1.mock.calls[0][0]).toBe(err); + expect(axios.isAxiosError(err)).toBeTruthy(); + } + + try { + await axiosObj.request(c2); + } catch (err) { + expect(e2).toBeCalled(); + expect(e2.mock.calls[0][0]).toBe(err); + expect(axios.isAxiosError(err)).toBeTruthy(); + } + }); + + test('应该支持异步错误处理', async () => { + const e1 = vi.fn(); + const e2 = vi.fn(); + const c1 = { + adapter: mockAdapterError(), + url: 'test', + errorHandler: async (err: unknown) => { + e1(err); + }, + }; + const c2 = { + adapter: mockAdapterFail(), + url: 'test', + errorHandler: async (err: unknown) => { + e2(err); + }, + }; + + try { + await axiosObj.request(c1); + } catch (err) { + expect(e1).toBeCalled(); + expect(e1.mock.calls[0][0]).toBe(err); + expect(axios.isAxiosError(err)).toBeTruthy(); + } + + try { + await axiosObj.request(c2); + } catch (err) { + expect(e2).toBeCalled(); + expect(e2.mock.calls[0][0]).toBe(err); + expect(axios.isAxiosError(err)).toBeTruthy(); + } + }); + test('应该支持添加/移除请求拦截器', async () => { const cb = vi.fn((v) => v); - const id = axios.interceptors.request.use(cb); - await axios.request('/test', { + const id = axiosObj.interceptors.request.use(cb); + await axiosObj.request('/test', { adapter: mockAdapter(), }); expect(cb.mock.calls.length).toBe(1); - await axios.request('/test', { + await axiosObj.request('/test', { adapter: mockAdapter(), }); expect(cb.mock.calls.length).toBe(2); - axios.interceptors.request.eject(id); - await axios.request('/test', { + axiosObj.interceptors.request.eject(id); + await axiosObj.request('/test', { adapter: mockAdapter(), }); @@ -141,7 +212,7 @@ describe('src/core/Axios.ts', () => { }); test('添加多个请求拦截器时应该按添加顺序从后往前依次执行', () => { - const axios = new Axios(); + const axiosObj = new Axios(); const cb1 = vi.fn((v) => { expect(v.params.index).toBe(2); @@ -159,11 +230,11 @@ describe('src/core/Axios.ts', () => { return v; }); - axios.interceptors.request.use(cb1); - axios.interceptors.request.use(cb2); - axios.interceptors.request.use(cb3); + axiosObj.interceptors.request.use(cb1); + axiosObj.interceptors.request.use(cb2); + axiosObj.interceptors.request.use(cb3); - axios.request('/test', { + axiosObj.request('/test', { adapter: (config) => { expect(config.params!.index).toBe(3); }, @@ -174,7 +245,7 @@ describe('src/core/Axios.ts', () => { }); test('请求拦截器应该支持抛出异常', async () => { - const axios = new Axios(); + const axiosObj = new Axios(); const c = { adapter: vi.fn(), url: 'test' }; const body = (v: any) => { throw { ...v, throw: true }; @@ -184,11 +255,11 @@ describe('src/core/Axios.ts', () => { const res2 = vi.fn(body); const rej2 = vi.fn(body); - axios.interceptors.request.use(res1, rej1); - axios.interceptors.request.use(res2, rej2); + axiosObj.interceptors.request.use(res1, rej1); + axiosObj.interceptors.request.use(res2, rej2); try { - await axios.request(c); + await axiosObj.request(c); } catch (err) { expect(err).toEqual({ ...c, throw: true }); } @@ -205,21 +276,21 @@ describe('src/core/Axios.ts', () => { test('应该支持添加/移除响应拦截器', async () => { const cb = vi.fn((v) => v); - const id = axios.interceptors.response.use(cb); - await axios.request('/test', { + const id = axiosObj.interceptors.response.use(cb); + await axiosObj.request('/test', { adapter: mockAdapter(), }); expect(cb.mock.calls.length).toBe(1); - await axios.request('/test', { + await axiosObj.request('/test', { adapter: mockAdapter(), }); expect(cb.mock.calls.length).toBe(2); - axios.interceptors.response.eject(id); - await axios.request('/test', { + axiosObj.interceptors.response.eject(id); + await axiosObj.request('/test', { adapter: mockAdapter(), }); @@ -227,7 +298,7 @@ describe('src/core/Axios.ts', () => { }); test('添加多个响应拦截器时应该按添加顺序从前往后依次执行', async () => { - const axios = new Axios(); + const axiosObj = new Axios(); const cb1 = vi.fn((v) => { expect(v.data.index).toBe(0); @@ -245,11 +316,11 @@ describe('src/core/Axios.ts', () => { return v; }); - axios.interceptors.response.use(cb1); - axios.interceptors.response.use(cb2); - axios.interceptors.response.use(cb3); + axiosObj.interceptors.response.use(cb1); + axiosObj.interceptors.response.use(cb2); + axiosObj.interceptors.response.use(cb3); - const response = await axios.request<{ index: 0 }>('/test', { + const response = await axiosObj.request<{ index: 0 }>('/test', { adapter: mockAdapter({ data: { index: 0 } }), }); @@ -257,7 +328,7 @@ describe('src/core/Axios.ts', () => { }); test('响应拦截器应该支持抛出异常', async () => { - const axios = new Axios(); + const axiosObj = new Axios(); const c = { adapter: vi.fn(mockAdapter()), url: 'test' }; const body = () => { throw { throw: true }; @@ -267,11 +338,11 @@ describe('src/core/Axios.ts', () => { const res2 = vi.fn(body); const rej2 = vi.fn(body); - axios.interceptors.response.use(res1, rej1); - axios.interceptors.response.use(res2, rej2); + axiosObj.interceptors.response.use(res1, rej1); + axiosObj.interceptors.response.use(res2, rej2); try { - await axios.request(c); + await axiosObj.request(c); } catch (err) { expect(err).toEqual({ throw: true }); } @@ -283,22 +354,24 @@ describe('src/core/Axios.ts', () => { }); test('应该可以获取 URI', () => { - expect(axios.getUri({ url: 'test' })).toBe('test'); - expect(axios.getUri({ url: 'test', params: { id: 1 } })).toBe('test?id=1'); - expect(axios.getUri({ url: 'test', paramsSerializer: () => 'id=1' })).toBe( + expect(axiosObj.getUri({ url: 'test' })).toBe('test'); + expect(axiosObj.getUri({ url: 'test', params: { id: 1 } })).toBe( 'test?id=1', ); + expect( + axiosObj.getUri({ url: 'test', paramsSerializer: () => 'id=1' }), + ).toBe('test?id=1'); }); test('派生的领域应该为 AxiosDomain 的实例', () => { - expect(axios.fork() instanceof AxiosDomain).toBeTruthy(); + expect(axiosObj.fork() instanceof AxiosDomain).toBeTruthy(); }); test('应该支持 绝对路径/相对路径 派生领域', () => { - const a1 = axios.fork({ baseURL: 'test' }); + const a1 = axiosObj.fork({ baseURL: 'test' }); const a2 = new Axios().fork({ baseURL: 'test' }); - const a3 = axios.fork({ baseURL: 'https://api.com' }); - const a4 = axios.fork(); + const a3 = axiosObj.fork({ baseURL: 'https://api.com' }); + const a4 = axiosObj.fork(); expect(a1.defaults.baseURL).toBe('http://api.com/test'); expect(a2.defaults.baseURL).toBe('/test'); @@ -307,14 +380,14 @@ describe('src/core/Axios.ts', () => { }); test('派生自当前实例的领域应该可以复用当前实例的拦截器', async () => { - const axios = new Axios(); + const axiosObj = new Axios(); const req = vi.fn((v) => v); const res = vi.fn((v) => v); - axios.interceptors.request.use(req); - axios.interceptors.response.use(res); + axiosObj.interceptors.request.use(req); + axiosObj.interceptors.response.use(res); - const a = axios.fork({ baseURL: 'test' }); + const a = axiosObj.fork({ baseURL: 'test' }); await a.request('test', { adapter: mockAdapter() }); expect(req).toBeCalled(); diff --git a/test/core/dispatchRequest.test.ts b/test/core/dispatchRequest.test.ts index 8205455..93ad183 100644 --- a/test/core/dispatchRequest.test.ts +++ b/test/core/dispatchRequest.test.ts @@ -1,10 +1,5 @@ import { describe, test, expect, vi } from 'vitest'; -import { - asyncNext, - mockAdapter, - mockAdapterError, - mockAdapterFail, -} from 'scripts/test.utils'; +import { asyncNext, mockAdapter } from 'scripts/test.utils'; import Axios from '@/core/Axios'; import { dispatchRequest } from '@/core/dispatchRequest'; import axios from '@/axios'; @@ -154,76 +149,6 @@ describe('src/core/dispatchRequest.ts', () => { expect(r.data).toEqual({ result: 1 }); }); - test('应该支持错误处理', async () => { - const e1 = vi.fn(); - const e2 = vi.fn(); - const c1 = { - ...defaults, - adapter: mockAdapterError(), - url: 'test', - errorHandler: e1, - }; - const c2 = { - ...defaults, - adapter: mockAdapterFail(), - url: 'test', - errorHandler: e2, - }; - - try { - await dispatchRequest(c1); - } catch (err) { - expect(e1).toBeCalled(); - expect(e1.mock.calls[0][0]).toBe(err); - expect(axios.isAxiosError(err)).toBeTruthy(); - } - - try { - await dispatchRequest(c2); - } catch (err) { - expect(e2).toBeCalled(); - expect(e2.mock.calls[0][0]).toBe(err); - expect(axios.isAxiosError(err)).toBeTruthy(); - } - }); - - test('应该支持异步错误处理', async () => { - const e1 = vi.fn(); - const e2 = vi.fn(); - const c1 = { - ...defaults, - adapter: mockAdapterError(), - url: 'test', - errorHandler: async (err: unknown) => { - e1(err); - }, - }; - const c2 = { - ...defaults, - adapter: mockAdapterFail(), - url: 'test', - errorHandler: async (err: unknown) => { - e2(err); - }, - }; - - try { - await dispatchRequest(c1); - } catch (err) { - expect(e1).toBeCalled(); - expect(e1.mock.calls[0][0]).toBe(err); - expect(axios.isAxiosError(err)).toBeTruthy(); - } - - try { - await dispatchRequest(c2); - } catch (err) { - expect(e2).toBeCalled(); - expect(e2.mock.calls[0][0]).toBe(err); - expect(axios.isAxiosError(err)).toBeTruthy(); - } - }); - test('请求发送前取消请求应该抛出异常', async () => { const cb = vi.fn(); const { cancel, token } = axios.CancelToken.source();