From 222b935f6839ce8fcecfa951d937e6160211f7f9 Mon Sep 17 00:00:00 2001 From: zjx0905 <954270063@qq.com> Date: Thu, 6 Apr 2023 22:59:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20=E6=B4=BE=E7=94=9F=E9=A2=86=E5=9F=9F=20axios.fork()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/Axios.ts | 173 +++--------------------- src/core/AxiosDomain.ts | 221 +++++++++++++++++++++++++++++++ test/axios.api.test.ts | 7 +- test/core/Axios.test.ts | 7 +- test/core/AxiosDomain.test.ts | 96 ++++++++++++++ test/core/flattenHeaders.test.ts | 2 +- test/core/generateType.test.ts | 2 +- 7 files changed, 345 insertions(+), 163 deletions(-) create mode 100644 src/core/AxiosDomain.ts create mode 100644 test/core/AxiosDomain.test.ts diff --git a/src/core/Axios.ts b/src/core/Axios.ts index 6871960..3737a02 100644 --- a/src/core/Axios.ts +++ b/src/core/Axios.ts @@ -1,4 +1,6 @@ import { buildURL } from '../helpers/buildURL'; +import { isAbsoluteURL } from '../helpers/isAbsoluteURL'; +import { combineURL } from '../helpers/combineURL'; import { mergeConfig } from './mergeConfig'; import { AxiosAdapter, @@ -12,6 +14,7 @@ import { CancelToken } from './cancel'; import dispatchRequest from './dispatchRequest'; import InterceptorManager from './InterceptorManager'; import { AxiosTransformer } from './transformData'; +import AxiosDomain from './AxiosDomain'; export type AxiosRequestMethod = | AxiosAdapterRequestMethod @@ -188,72 +191,7 @@ export interface AxiosConstructor { new (config: AxiosRequestConfig): Axios; } -export interface AxiosAliasMethod { - ( - /** - * 请求地址 - */ - url: string, - /** - * 请求配置 - */ - config?: AxiosRequestConfig, - ): Promise>; -} - -export interface AxiosWithParamsAliasMethod { - ( - /** - * 请求地址 - */ - url: string, - /** - * 请求参数 - */ - params?: AnyObject, - /** - * 请求配置 - */ - config?: AxiosRequestConfig, - ): Promise>; -} - -export interface AxiosWithDataAliasMethod { - ( - /** - * 请求地址 - */ - url: string, - /** - * 请求数据 - */ - data?: AnyObject, - /** - * 请求配置 - */ - config?: AxiosRequestConfig, - ): Promise>; -} - -export default class Axios { - /** - * 普通请求别名 - */ - static as = ['options', 'trace', 'connect'] as const; - /** - * 带请求参数的请求别名 - */ - static pas = ['head', 'get', 'delete'] as const; - /** - * 带请求数据的请求别名 - */ - static das = ['post', 'put'] as const; - - /** - * 默认请求配置 - */ - defaults: AxiosRequestConfig; - +export default class Axios extends AxiosDomain { /** * 拦截器 */ @@ -268,85 +206,8 @@ export default class Axios { response: new InterceptorManager(), }; - /** - * 发送 options 请求 - */ - options!: AxiosAliasMethod; - - /** - * 发送 get 请求 - */ - get!: AxiosWithParamsAliasMethod; - - /** - * 发送 head 请求 - */ - head!: AxiosWithParamsAliasMethod; - - /** - * 发送 post 请求 - */ - post!: AxiosWithDataAliasMethod; - - /** - * 发送 put 请求 - */ - put!: AxiosWithDataAliasMethod; - - /** - * 发送 delete 请求 - */ - delete!: AxiosWithParamsAliasMethod; - - /** - * 发送 trace 请求 - */ - trace!: AxiosAliasMethod; - - /** - * 发送 connect 请求 - */ - connect!: AxiosAliasMethod; - - /** - * 实例化 - * - * @param defaults 默认配置 - */ constructor(defaults: AxiosRequestConfig = {}) { - this.defaults = defaults; - - for (const alias of Axios.as) { - this[alias] = (url, config = {}) => { - return this.request({ - ...config, - method: alias, - url, - }); - }; - } - - for (const alias of Axios.pas) { - this[alias] = (url, params, config = {}) => { - return this.request({ - ...config, - method: alias, - params, - url, - }); - }; - } - - for (const alias of Axios.das) { - this[alias] = (url, data, config = {}) => { - return this.request({ - ...config, - method: alias, - data, - url, - }); - }; - } + super(defaults, (...args) => this.#processRequest(...args)); } getUri(config: AxiosRequestConfig): string { @@ -354,22 +215,25 @@ export default class Axios { this.defaults, config, ); - return buildURL(url, params, paramsSerializer).replace(/^\?/, ''); } /** - * 发送请求 - * - * @param config 请求配置 + * 派生领域 */ - request( - config: AxiosRequestConfig, - ): Promise> { - const requestConfig = mergeConfig(this.defaults, config); + fork(defaults: AxiosRequestConfig) { + const config = mergeConfig(this.defaults, defaults); + const { baseURL = '' } = defaults; + if (!isAbsoluteURL(baseURL)) { + config.baseURL = combineURL(this.defaults.baseURL ?? '', baseURL); + } + return new AxiosDomain(config, this.#processRequest); + } + + #processRequest = (config: AxiosRequestConfig) => { const { request, response } = this.interceptors; - let promiseRequest = Promise.resolve(requestConfig); + let promiseRequest = Promise.resolve(config); request.forEach(({ resolved, rejected }) => { promiseRequest = promiseRequest.then( resolved, @@ -382,7 +246,6 @@ export default class Axios { AxiosResponse >; }); - return promiseResponse as Promise>; - } + }; } diff --git a/src/core/AxiosDomain.ts b/src/core/AxiosDomain.ts new file mode 100644 index 0000000..1ef8e83 --- /dev/null +++ b/src/core/AxiosDomain.ts @@ -0,0 +1,221 @@ +import { isPlainObject, isString } from '../helpers/isTypes'; +import { AxiosRequestConfig, AxiosRequestData, AxiosResponse } from './Axios'; +import { mergeConfig } from './mergeConfig'; + +export interface AxiosDomainAsRequest { + ( + /** + * 请求配置 + */ + config?: AxiosRequestConfig, + ): Promise>; + ( + /** + * 请求地址 + */ + url: string, + /** + * 请求配置 + */ + config?: AxiosRequestConfig, + ): Promise>; +} + +export interface AxiosDomainAsRequestWithParams { + ( + /** + * 请求参数 + */ + params?: AnyObject, + /** + * 请求配置 + */ + config?: AxiosRequestConfig, + ): Promise>; + ( + /** + * 请求地址 + */ + url: string, + /** + * 请求参数 + */ + params?: AnyObject, + /** + * 请求配置 + */ + config?: AxiosRequestConfig, + ): Promise>; +} + +export interface AxiosDomainAsRequestWithData { + ( + /** + * 请求数据 + */ + data?: AnyObject, + /** + * 请求配置 + */ + config?: AxiosRequestConfig, + ): Promise>; + ( + /** + * 请求地址 + */ + url: string, + /** + * 请求数据 + */ + data?: AnyObject, + /** + * 请求配置 + */ + config?: AxiosRequestConfig, + ): Promise>; +} + +export interface AxiosDomainRequest { + (config: AxiosRequestConfig): Promise>; +} + +export default class AxiosDomain { + /** + * 普通请求别名 + */ + static as = ['options', 'trace', 'connect'] as const; + + /** + * 带请求参数的请求别名 + */ + static asp = ['head', 'get', 'delete'] as const; + + /** + * 带请求数据的请求别名 + */ + static asd = ['post', 'put'] as const; + + /** + * 默认请求配置 + */ + defaults: AxiosRequestConfig; + + /** + * 发送请求 + */ + request!: AxiosDomainRequest; + + /** + * 发送 options 请求 + */ + options!: AxiosDomainAsRequest; + + /** + * 发送 get 请求 + */ + get!: AxiosDomainAsRequestWithParams; + + /** + * 发送 head 请求 + */ + head!: AxiosDomainAsRequestWithParams; + + /** + * 发送 post 请求 + */ + post!: AxiosDomainAsRequestWithData; + + /** + * 发送 put 请求 + */ + put!: AxiosDomainAsRequestWithData; + + /** + * 发送 delete 请求 + */ + delete!: AxiosDomainAsRequestWithParams; + + /** + * 发送 trace 请求 + */ + trace!: AxiosDomainAsRequest; + + /** + * 发送 connect 请求 + */ + connect!: AxiosDomainAsRequest; + + constructor( + defaults: AxiosRequestConfig = {}, + processRequest: AxiosDomainRequest, + ) { + this.defaults = defaults; + this.request = (config: AxiosRequestConfig) => { + return processRequest(mergeConfig(this.defaults, config)); + }; + + this.#createAsRequests(); + this.#createAspRequests(); + this.#createAsdRequests(); + } + + #createAsRequests() { + for (const alias of AxiosDomain.as) { + this[alias] = function processAsRequest( + urlOrConfig?: string | AxiosRequestConfig, + config: AxiosRequestConfig = {}, + ) { + if (isString(urlOrConfig)) { + config.url = urlOrConfig; + } else if (isPlainObject(urlOrConfig)) { + config = urlOrConfig; + } + config.method = alias; + + return this.request(config); + }; + } + } + + #createAspRequests() { + for (const alias of AxiosDomain.asp) { + this[alias] = function processAspRequest( + urlOrParams?: string | AxiosRequestConfig, + paramsOrConfig: AxiosRequestConfig | AnyObject = {}, + config: AxiosRequestConfig = {}, + ) { + if (isString(urlOrParams)) { + config.url = urlOrParams; + config.params = paramsOrConfig; + } else if (isPlainObject(urlOrParams)) { + config = paramsOrConfig; + config.params = urlOrParams; + } + config.method = alias; + + return this.request(config); + }; + } + } + + #createAsdRequests() { + for (const alias of AxiosDomain.asd) { + this[alias] = function processAsdRequest( + urlOrData?: string | AxiosRequestConfig, + dataOrConfig: AxiosRequestData | AxiosRequestConfig = {}, + config: AxiosRequestConfig = {}, + ) { + if (isString(urlOrData)) { + config.url = urlOrData; + config.data = dataOrConfig; + } else if (isPlainObject(urlOrData)) { + config = dataOrConfig; + config.data = urlOrData; + } + config.method = alias; + + return this.request(config); + }; + } + } +} diff --git a/test/axios.api.test.ts b/test/axios.api.test.ts index d9f5b9d..53357c3 100644 --- a/test/axios.api.test.ts +++ b/test/axios.api.test.ts @@ -33,8 +33,9 @@ describe('src/axios.ts', () => { expect(axios.request).toBeTypeOf('function'); expect(axios.getUri).toBeTypeOf('function'); + expect(axios.fork).toBeTypeOf('function'); - [...Axios.as, ...Axios.pas, ...Axios.das].forEach((k) => { + [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => { expect(axios[k]).toBeTypeOf('function'); }); }); @@ -77,7 +78,7 @@ describe('src/axios.ts', () => { }), }; - Axios.pas.forEach((a) => { + Axios.asp.forEach((a) => { axios[a]('test', p, c1).then((res) => { expect(res.data).toEqual(data); }); @@ -108,7 +109,7 @@ describe('src/axios.ts', () => { }), }; - Axios.das.forEach((a) => { + Axios.asd.forEach((a) => { axios[a]('test', d, c1).then((res) => { expect(res.data).toEqual(data); }); diff --git a/test/core/Axios.test.ts b/test/core/Axios.test.ts index 18496af..c7c1315 100644 --- a/test/core/Axios.test.ts +++ b/test/core/Axios.test.ts @@ -4,8 +4,8 @@ import Axios from 'src/core/Axios'; describe('src/core/Axios.ts', () => { test('应该有这些静态属性', () => { expect(Axios.as).toEqual(['options', 'trace', 'connect']); - expect(Axios.pas).toEqual(['head', 'get', 'delete']); - expect(Axios.das).toEqual(['post', 'put']); + expect(Axios.asp).toEqual(['head', 'get', 'delete']); + expect(Axios.asd).toEqual(['post', 'put']); }); test('应该有这些实例属性', () => { @@ -18,8 +18,9 @@ describe('src/core/Axios.ts', () => { expect(a.interceptors).toBeTypeOf('object'); expect(a.request).toBeTypeOf('function'); expect(a.getUri).toBeTypeOf('function'); + expect(a.fork).toBeTypeOf('function'); - [...Axios.as, ...Axios.pas, ...Axios.das].forEach((k) => { + [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => { expect(a[k]).toBeTypeOf('function'); }); }); diff --git a/test/core/AxiosDomain.test.ts b/test/core/AxiosDomain.test.ts new file mode 100644 index 0000000..7a9e6ae --- /dev/null +++ b/test/core/AxiosDomain.test.ts @@ -0,0 +1,96 @@ +import { describe, test, expect, vi } from 'vitest'; +import AxiosDomain, { AxiosDomainRequest } from 'src/core/AxiosDomain'; +import { ignore } from 'src/helpers/ignore'; + +describe('src/core/Axios.ts', () => { + test('应该有这些静态属性', () => { + expect(AxiosDomain.as).toEqual(['options', 'trace', 'connect']); + expect(AxiosDomain.asp).toEqual(['head', 'get', 'delete']); + expect(AxiosDomain.asd).toEqual(['post', 'put']); + }); + + test('应该有这些实例属性', () => { + const c = { + baseURL: 'http://api.com', + }; + const a = new AxiosDomain(c, vi.fn()); + + expect(a.defaults).toEqual(c); + expect(a.request).toBeTypeOf('function'); + + [...AxiosDomain.as, ...AxiosDomain.asp, ...AxiosDomain.asd].forEach((k) => { + expect(a[k]).toBeTypeOf('function'); + }); + }); + + test('发送请求时 processRequest 应该被调用', () => { + const p = vi.fn(); + const d = { + baseURL: 'http://api.com', + }; + const c = { + url: 'test', + params: { + id: 1, + }, + data: { + id: 1, + }, + }; + + new AxiosDomain(d, p).request(c); + + expect(p).toBeCalled(); + expect(p.mock.calls[0][0]).toEqual({ + ...d, + ...c, + }); + }); + + test('应该可以调用这些方法', () => { + const cb = vi.fn(); + const d = { + baseURL: 'http://api.com', + }; + const c = { + url: 'test', + params: { + id: 1, + }, + data: { + id: 1, + }, + }; + const a = new AxiosDomain(d, ((config) => { + cb(); + + expect(config.baseURL).toBe(d.baseURL); + expect(config.url).toBe(c.url); + expect(config.params).toEqual(c.params); + expect(config.data).toEqual(c.data); + }) as AxiosDomainRequest); + + a.request(c); + + AxiosDomain.as.forEach((k) => a[k](c)); + AxiosDomain.as.forEach((k) => a[k](c.url, ignore(c, 'url'))); + + AxiosDomain.asp.forEach((k) => a[k](c.params, ignore(c, 'params'))); + AxiosDomain.asp.forEach((k) => + a[k](c.url, c.params, ignore(c, 'url', 'params')), + ); + + AxiosDomain.asd.forEach((k) => a[k](c.data, ignore(c, 'data'))); + AxiosDomain.asd.forEach((k) => + a[k](c.url, c.data, ignore(c, 'url', 'data')), + ); + + const t = + (AxiosDomain.as.length + + AxiosDomain.asp.length + + AxiosDomain.asd.length) * + 2 + + 1; + expect(cb.mock.calls.length).toBe(t); + }); +}); diff --git a/test/core/flattenHeaders.test.ts b/test/core/flattenHeaders.test.ts index 5bac678..23584b1 100644 --- a/test/core/flattenHeaders.test.ts +++ b/test/core/flattenHeaders.test.ts @@ -3,7 +3,7 @@ import { flattenHeaders } from 'src/core/flattenHeaders'; import Axios from 'src/core/Axios'; describe('src/core/flattenHeaders.ts', () => { - const keys = [...Axios.as, ...Axios.pas, ...Axios.das]; + const keys = [...Axios.as, ...Axios.asp, ...Axios.asd]; const baseHeaders = { options: { v1: 'options1', diff --git a/test/core/generateType.test.ts b/test/core/generateType.test.ts index 961cfc1..3081f89 100644 --- a/test/core/generateType.test.ts +++ b/test/core/generateType.test.ts @@ -4,7 +4,7 @@ import Axios from 'src/core/Axios'; describe('src/core/generateType.ts', () => { test('应该是一个 reuqest', () => { - for (const a of [...Axios.as, ...Axios.pas, ...Axios.das]) { + for (const a of [...Axios.as, ...Axios.asp, ...Axios.asd]) { expect(generateType({ method: a })).toBe('request'); } });