diff --git a/src/adapter.ts b/src/adapter.ts index 502441d..696271f 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -11,8 +11,14 @@ import { AxiosRequestHeaders, } from './core/Axios'; +/** + * 适配器请求类型 + */ export type AxiosAdapterRequestType = 'request' | 'download' | 'upload'; +/** + * 适配器请求方法 + */ export type AxiosAdapterRequestMethod = | 'OPTIONS' | 'GET' @@ -24,8 +30,19 @@ export type AxiosAdapterRequestMethod = | 'TRACE' | 'CONNECT'; +/** + * 适配器请求数据 + */ +export type AxiosAdapterRequestData = string | AnyObject | ArrayBuffer; + +/** + * 适配器响应数据 + */ export type AxiosAdapterResponseData = string | ArrayBuffer | AnyObject; +/** + * 适配器响应体 + */ export interface AxiosAdapterResponse extends AnyObject { /** * 状态码 @@ -45,6 +62,9 @@ export interface AxiosAdapterResponse extends AnyObject { data: AxiosAdapterResponseData; } +/** + * 适配器错误体 + */ export interface AxiosAdapterResponseError extends AnyObject { /** * 状态码 @@ -64,6 +84,9 @@ export interface AxiosAdapterResponseError extends AnyObject { data?: AnyObject; } +/** + * 适配器请求配置 + */ export interface AxiosAdapterRequestConfig extends AnyObject { /** * 请求类型 @@ -84,7 +107,7 @@ export interface AxiosAdapterRequestConfig extends AnyObject { /** * 请求数据 */ - data?: AnyObject; + data?: AxiosAdapterRequestData; /** * 请求头 */ @@ -111,14 +134,30 @@ export interface AxiosAdapterRequestConfig extends AnyObject { fail(error: AxiosAdapterResponseError): void; } +/** + * 请求函数基本选项 + */ export interface AxiosAdapterBaseOptions extends AxiosAdapterRequestConfig { header?: AxiosRequestHeaders; success(response: AxiosAdapterResponse): void; fail(error: AxiosAdapterResponseError): void; } +/** + * 请求函数选项 + */ export type AxiosAdapterRequestOptions = AxiosAdapterBaseOptions; +/** + * 下载函数选项 + */ +export interface AxiosAdapterDownloadOptions extends AxiosAdapterBaseOptions { + filePath?: string; +} + +/** + * 上传函数选项 + */ export interface AxiosAdapterUploadOptions extends AxiosAdapterBaseOptions, AxiosRequestFormData { @@ -126,22 +165,30 @@ export interface AxiosAdapterUploadOptions formData?: AnyObject; } -export interface AxiosAdapterDownloadOptions extends AxiosAdapterBaseOptions { - filePath?: string; -} - +/** + * 请求函数 + */ export interface AxiosAdapterRequest { - (config: AxiosAdapterRequestOptions): AxiosAdapterTask; -} - -export interface AxiosAdapterUpload { - (config: AxiosAdapterUploadOptions): AxiosAdapterTask; + (config: AxiosAdapterRequestOptions): AxiosAdapterPlatformTask; } +/** + * 下载函数 + */ export interface AxiosAdapterDownload { - (config: AxiosAdapterDownloadOptions): AxiosAdapterTask; + (config: AxiosAdapterDownloadOptions): AxiosAdapterPlatformTask; } +/** + * 上传函数 + */ +export interface AxiosAdapterUpload { + (config: AxiosAdapterUploadOptions): AxiosAdapterPlatformTask; +} + +/** + * 适配器平台 + */ export interface AxiosAdapterPlatform { /** * 发送请求 @@ -157,7 +204,10 @@ export interface AxiosAdapterPlatform { upload: AxiosAdapterUpload; } -export type AxiosAdapterTask = +/** + * 适配器平台请求任务 + */ +export type AxiosAdapterPlatformTask = | undefined | void | { @@ -166,10 +216,16 @@ export type AxiosAdapterTask = offProgressUpdate?(callback: AxiosProgressCallback): void; }; +/** + * 适配器函数 + */ export interface AxiosAdapter { - (config: AxiosAdapterRequestConfig): AxiosAdapterTask; + (config: AxiosAdapterRequestConfig): AxiosAdapterPlatformTask; } +/** + * 获取支持的平台适配器 + */ export function getDefaultAdapter() { const platform = revisePlatformApiNames(getPlatform()); @@ -220,13 +276,20 @@ export function getDefaultAdapter() { return createAdapter(platform); } +/** + * 创建适配器 + * + * @param platform 平台 API 对象 + */ export function createAdapter(platform: AxiosAdapterPlatform) { assert(isPlainObject(platform), 'platform 不是一个 object'); assert(isFunction(platform.request), 'request 不是一个 function'); assert(isFunction(platform.upload), 'upload 不是一个 function'); assert(isFunction(platform.download), 'download 不是一个 function'); - function adapter(config: AxiosAdapterRequestConfig): AxiosAdapterTask { + function adapter( + config: AxiosAdapterRequestConfig, + ): AxiosAdapterPlatformTask { const baseOptions = transformOptions(config); switch (config.type) { @@ -242,14 +305,14 @@ export function createAdapter(platform: AxiosAdapterPlatform) { function processRequest( request: AxiosAdapterRequest, baseOptions: AxiosAdapterBaseOptions, - ): AxiosAdapterTask { + ): AxiosAdapterPlatformTask { return request(baseOptions); } function processUpload( upload: AxiosAdapterUpload, baseOptions: AxiosAdapterBaseOptions, - ): AxiosAdapterTask { + ): AxiosAdapterPlatformTask { const { name, filePath, fileType, ...formData } = baseOptions.data as AxiosRequestFormData; const options = { @@ -273,7 +336,7 @@ export function createAdapter(platform: AxiosAdapterPlatform) { function processDownload( download: AxiosAdapterDownload, baseOptions: AxiosAdapterBaseOptions, - ): AxiosAdapterTask { + ): AxiosAdapterPlatformTask { const options: AxiosAdapterDownloadOptions = { ...baseOptions, filePath: baseOptions.params?.filePath, diff --git a/src/axios.ts b/src/axios.ts index 3c0a74b..a5895a3 100644 --- a/src/axios.ts +++ b/src/axios.ts @@ -10,6 +10,9 @@ import { mergeConfig } from './core/mergeConfig'; import { createAdapter } from './adapter'; import defaults from './defaults'; +/** + * axios 实例默认配置 + */ export interface AxiosInstanceDefaults extends AxiosRequestConfig { /** * 请求头 @@ -17,6 +20,9 @@ export interface AxiosInstanceDefaults extends AxiosRequestConfig { headers: Required; } +/** + * axios 实例 + */ export interface AxiosInstance extends AxiosDomainRequest, Axios { /** * 默认请求配置 @@ -24,6 +30,9 @@ export interface AxiosInstance extends AxiosDomainRequest, Axios { defaults: AxiosInstanceDefaults; } +/** + * axios 静态对象 + */ export interface AxiosStatic extends AxiosInstance { /** * Axios 类 @@ -44,11 +53,11 @@ export interface AxiosStatic extends AxiosInstance { */ createAdapter: typeof createAdapter; /** - * 判断 Cancel + * 传入取消请求错误返回 true */ isCancel: typeof isCancel; /** - * 判断 AxiosError + * 传入响应错误返回 true */ isAxiosError: typeof isAxiosError; } diff --git a/src/core/Axios.ts b/src/core/Axios.ts index 4ef61c6..7f90882 100644 --- a/src/core/Axios.ts +++ b/src/core/Axios.ts @@ -5,7 +5,7 @@ import { isFunction, isPromise, isString } from '../helpers/isTypes'; import { AxiosAdapter, AxiosAdapterRequestMethod, - AxiosAdapterTask, + AxiosAdapterPlatformTask, AxiosAdapterRequestConfig, AxiosAdapterResponseData, } from '../adapter'; @@ -14,9 +14,11 @@ import { mergeConfig } from './mergeConfig'; import { CancelToken } from './cancel'; import { dispatchRequest } from './dispatchRequest'; import { AxiosTransformer } from './transformData'; - import AxiosDomain from './AxiosDomain'; +/** + * 请求方法 + */ export type AxiosRequestMethod = | AxiosAdapterRequestMethod | 'options' @@ -29,6 +31,9 @@ export type AxiosRequestMethod = | 'trace' | 'connect'; +/** + * 请求头 + */ export interface AxiosRequestHeaders extends AnyObject { /** * 通用请求头 @@ -68,6 +73,9 @@ export interface AxiosRequestHeaders extends AnyObject { connect?: AnyObject; } +/** + * 表单数据(上传会用到) + */ export interface AxiosRequestFormData extends AnyObject { /** * 文件名 @@ -79,10 +87,23 @@ export interface AxiosRequestFormData extends AnyObject { filePath: string; } -export type AxiosRequestData = AnyObject | AxiosRequestFormData; +/** + * 请求数据 + */ +export type AxiosRequestData = + | string + | AnyObject + | ArrayBuffer + | AxiosRequestFormData; +/** + * 响应数据 + */ export type AxiosResponseData = undefined | number | AxiosAdapterResponseData; +/** + * 监听进度回调事件对象 + */ export interface AxiosProgressEvent { /** * 下载进度 @@ -98,10 +119,21 @@ export interface AxiosProgressEvent { totalBytesExpectedToSend: number; } +/** + * 监听进度回调 + */ export interface AxiosProgressCallback { - (event: AxiosProgressEvent): void; + ( + /** + * 事件对象 + */ + event: AxiosProgressEvent, + ): void; } +/** + * 请求配置 + */ export interface AxiosRequestConfig extends Partial< Omit @@ -176,6 +208,9 @@ export interface AxiosRequestConfig validateStatus?: (status: number) => boolean; } +/** + * 响应体 + */ export interface AxiosResponse< TData extends AxiosResponseData = AxiosResponseData, > extends AnyObject { @@ -202,9 +237,12 @@ export interface AxiosResponse< /** * 请求任务 */ - request?: AxiosAdapterTask; + request?: AxiosAdapterPlatformTask; } +/** + * 错误体 + */ export interface AxiosResponseError extends AnyObject { /** * 状态码 @@ -233,9 +271,12 @@ export interface AxiosResponseError extends AnyObject { /** * 请求任务 */ - request?: AxiosAdapterTask; + request?: AxiosAdapterPlatformTask; } +/** + * Axios 构造函数 + */ export interface AxiosConstructor { new (config: AxiosRequestConfig): Axios; } diff --git a/src/core/AxiosDomain.ts b/src/core/AxiosDomain.ts index 725bb6d..87b1b99 100644 --- a/src/core/AxiosDomain.ts +++ b/src/core/AxiosDomain.ts @@ -8,71 +8,48 @@ import { AxiosResponseData, } from './Axios'; +/** + * 请求函数 + */ export interface AxiosDomainRequest { + (config: AxiosRequestConfig): Promise< + AxiosResponse + >; ( - /** - * 请求配置 - */ - config: AxiosRequestConfig, - ): Promise>; - ( - /** - * 请求地址 - */ url: string, - /** - * 请求配置 - */ config?: AxiosRequestConfig, ): Promise>; } -export interface AxiosDomainRequestMethod { - ( - /** - * 请求地址 - */ - url: string, - /** - * 请求配置 - */ - config?: AxiosRequestConfig, - ): Promise>; -} +/** + * 普通的请求方法 + */ +export type AxiosDomainRequestMethod = ( + url: string, + config?: AxiosRequestConfig, +) => Promise>; -export interface AxiosDomainRequestMethodWithParams { - ( - /** - * 请求地址 - */ - url: string, - /** - * 请求参数 - */ - params?: AnyObject, - /** - * 请求配置 - */ - config?: AxiosRequestConfig, - ): Promise>; -} +/** + * 带参数的请求方法 + */ +export type AxiosDomainRequestMethodWithParams = < + TData extends AxiosResponseData, +>( + url: string, + params?: AnyObject, + config?: AxiosRequestConfig, +) => Promise>; -export interface AxiosDomainRequestMethodWithData { - ( - /** - * 请求地址 - */ - url: string, - /** - * 请求数据 - */ - data?: AxiosRequestData, - /** - * 请求配置 - */ - config?: AxiosRequestConfig, - ): Promise>; -} +/** + * 带数据的请求方法 + */ +export type AxiosDomainRequestMethodWithData = < + TData extends AxiosResponseData, +>( + url: string, + data?: AxiosRequestData, + config?: AxiosRequestConfig, +) => Promise>; /** * 普通的请求方法名称 @@ -187,7 +164,7 @@ for (const method of requestMethodWithParamsNames) { config = {}, ) { config.method = method; - config.params = config.params ? deepMerge(params, config.params) : params; + config.params = deepMerge(params, config.params ?? {}); return this.request(url, config); }; } @@ -195,11 +172,11 @@ for (const method of requestMethodWithParamsNames) { for (const method of requestMethodWithDataNames) { AxiosDomain.prototype[method] = function processRequestMethodWithData( url, - data = {}, + data, config = {}, ) { config.method = method; - config.data = config.data ? deepMerge(data, config.data) : data; + config.data = data; return this.request(url, config); }; } diff --git a/src/core/cancel.ts b/src/core/cancel.ts index 98c0475..bfd81c3 100644 --- a/src/core/cancel.ts +++ b/src/core/cancel.ts @@ -1,19 +1,12 @@ export interface CancelAction { - ( - /** - * 取消信息 - */ - message?: string, - ): void; + /** + * 取消信息 + */ + (message?: string): void; } export interface CancelExecutor { - ( - /** - * 取消函数 - */ - cancel: CancelAction, - ): void; + (cancel: CancelAction): void; } export interface CancelTokenSource { diff --git a/src/core/createError.ts b/src/core/createError.ts index 09d1fe7..d965d4c 100644 --- a/src/core/createError.ts +++ b/src/core/createError.ts @@ -1,19 +1,19 @@ import { cleanStack } from '../helpers/error'; -import { AxiosAdapterTask } from '../adapter'; +import { AxiosAdapterPlatformTask } from '../adapter'; import { AxiosRequestConfig, AxiosResponse, AxiosResponseError } from './Axios'; export type AxiosErrorResponse = AxiosResponse | AxiosResponseError; class AxiosError extends Error { config: AxiosRequestConfig; - request: AxiosAdapterTask; + request: AxiosAdapterPlatformTask; response: AxiosErrorResponse; constructor( message: string, config: AxiosRequestConfig, response: AxiosErrorResponse, - request: AxiosAdapterTask, + request: AxiosAdapterPlatformTask, ) { super(message); @@ -29,7 +29,7 @@ export function createError( message: string, config: AxiosRequestConfig, response: AxiosErrorResponse, - request: AxiosAdapterTask, + request: AxiosAdapterPlatformTask, ) { const axiosError = new AxiosError(message, config, response, request); cleanStack(axiosError); diff --git a/src/core/dispatchRequest.ts b/src/core/dispatchRequest.ts index 44f38b6..8ead70c 100644 --- a/src/core/dispatchRequest.ts +++ b/src/core/dispatchRequest.ts @@ -9,13 +9,6 @@ import { transformURL } from './transformURL'; import { AxiosErrorResponse } from './createError'; import { requestMethodWithDataNames } from './AxiosDomain'; -function throwIfCancellationRequested(config: AxiosRequestConfig) { - const { cancelToken } = config; - if (isCancelToken(cancelToken)) { - cancelToken.throwIfRequested(); - } -} - /** * 可以携带 data 的请求方法 */ @@ -24,6 +17,13 @@ const requestMethodWithDataRE = new RegExp( 'i', ); +/** + * 发送请求 + * + * 校验配置,转换配置,转换数据,捕获取消请求。 + * + * @param config 请求配置 + */ export function dispatchRequest(config: AxiosRequestConfig) { throwIfCancellationRequested(config); @@ -67,3 +67,10 @@ export function dispatchRequest(config: AxiosRequestConfig) { return request(config).then(onSuccess, onError); } + +function throwIfCancellationRequested(config: AxiosRequestConfig) { + const { cancelToken } = config; + if (isCancelToken(cancelToken)) { + cancelToken.throwIfRequested(); + } +} diff --git a/src/core/request.ts b/src/core/request.ts index 8982065..eb4b9b5 100644 --- a/src/core/request.ts +++ b/src/core/request.ts @@ -4,7 +4,7 @@ import { AxiosAdapterRequestMethod, AxiosAdapterResponse, AxiosAdapterResponseError, - AxiosAdapterTask, + AxiosAdapterPlatformTask, } from '../adapter'; import { AxiosProgressCallback, @@ -16,27 +16,13 @@ import { isCancelToken } from './cancel'; import { AxiosErrorResponse, createError } from './createError'; import { generateType } from './generateType'; -function tryToggleProgressUpdate( - adapterConfig: AxiosAdapterRequestConfig, - progressUpdate?: (callback: AxiosProgressCallback) => void, -) { - const { onUploadProgress, onDownloadProgress } = adapterConfig; - if (isFunction(progressUpdate)) { - switch (adapterConfig.type) { - case 'upload': - if (isFunction(onUploadProgress)) { - progressUpdate(onUploadProgress); - } - break; - case 'download': - if (isFunction(onDownloadProgress)) { - progressUpdate(onDownloadProgress); - } - break; - } - } -} - +/** + * 开始请求 + * + * 创建适配器配置并调用适配器,监听取消请求,注册监听回调,处理响应体和错误体,抛出异常。 + * + * @param config 请求配置 + */ export function request(config: AxiosRequestConfig) { return new Promise((resolve, reject) => { const { adapter, url, method, cancelToken } = config; @@ -50,7 +36,7 @@ export function request(config: AxiosRequestConfig) { fail, }; - let adapterTask: AxiosAdapterTask; + let adapterTask: AxiosAdapterPlatformTask; try { adapterTask = adapter!(adapterConfig); } catch (err) { @@ -60,8 +46,8 @@ export function request(config: AxiosRequestConfig) { }); } - function success(adapterResponse: AxiosAdapterResponse): void { - const response = adapterResponse as AxiosResponse; + function success(baseResponse: AxiosAdapterResponse): void { + const response = baseResponse as AxiosResponse; response.status = response.status ?? 200; response.statusText = response.statusText ?? 'OK'; response.headers = response.headers ?? {}; @@ -75,8 +61,8 @@ export function request(config: AxiosRequestConfig) { } } - function fail(adapterResponseError: AxiosAdapterResponseError): void { - const responseError = adapterResponseError as AxiosResponseError; + function fail(baseResponseError: AxiosAdapterResponseError): void { + const responseError = baseResponseError as AxiosResponseError; responseError.isFail = true; responseError.status = responseError.status ?? 400; responseError.statusText = responseError.statusText ?? 'Fail Adapter'; @@ -111,3 +97,24 @@ export function request(config: AxiosRequestConfig) { } }); } + +function tryToggleProgressUpdate( + adapterConfig: AxiosAdapterRequestConfig, + progressUpdate?: (callback: AxiosProgressCallback) => void, +) { + const { onUploadProgress, onDownloadProgress } = adapterConfig; + if (isFunction(progressUpdate)) { + switch (adapterConfig.type) { + case 'upload': + if (isFunction(onUploadProgress)) { + progressUpdate(onUploadProgress); + } + break; + case 'download': + if (isFunction(onDownloadProgress)) { + progressUpdate(onDownloadProgress); + } + break; + } + } +} diff --git a/src/core/transformURL.ts b/src/core/transformURL.ts index a28c7f3..a0e1c8d 100644 --- a/src/core/transformURL.ts +++ b/src/core/transformURL.ts @@ -1,3 +1,4 @@ +import { isPlainObject } from '../helpers/isTypes'; import { buildURL } from '../helpers/buildURL'; import { combineURL } from '../helpers/combineURL'; import { dynamicURL } from '../helpers/dynamicURL'; @@ -8,7 +9,11 @@ export function transformURL(config: AxiosRequestConfig) { let url = config.url ?? ''; if (!isAbsoluteURL(url)) url = combineURL(config.baseURL ?? '', url); - url = dynamicURL(url, config.params, config.data); + url = dynamicURL( + url, + config.params, + isPlainObject(config.data) ? config.data : {}, + ); url = buildURL(url, config.params, config.paramsSerializer); return url; diff --git a/src/index.ts b/src/index.ts index b2aa77b..8e8ef07 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,14 +19,14 @@ export type { AxiosAdapterResponse, AxiosAdapterResponseData, AxiosAdapterResponseError, - AxiosAdapterPlatform, AxiosAdapterRequest, AxiosAdapterRequestOptions, AxiosAdapterDownload, AxiosAdapterDownloadOptions, AxiosAdapterUpload, AxiosAdapterUploadOptions, - AxiosAdapterTask, + AxiosAdapterPlatform, + AxiosAdapterPlatformTask, } from './adapter'; export type { AxiosInstanceDefaults, diff --git a/test/core/AxiosDomain.test.ts b/test/core/AxiosDomain.test.ts index a55b0d8..5696bbb 100644 --- a/test/core/AxiosDomain.test.ts +++ b/test/core/AxiosDomain.test.ts @@ -196,33 +196,20 @@ describe('src/core/AxiosDomain.ts', () => { requestMethodWithParamsNames.forEach((k) => a[k]('test', p, c)); }); - test('应该支持深度合并 data', () => { + test('应该只取传入的 data', () => { const ds = { baseURL: 'http://api.com', }; const d = { - v1: 1, - v2: { - v1: 1, - }, + v: 1, }; const c = { - data: { - v2: { - v2: 2, - }, - v3: 3, - }, + v: 2, }; const a = new AxiosDomain(ds, async (config) => { expect(config.data).toEqual({ - v1: 1, - v2: { - v1: 1, - v2: 2, - }, - v3: 3, + v: 1, }); return {} as AxiosResponse; @@ -230,4 +217,36 @@ describe('src/core/AxiosDomain.ts', () => { requestMethodWithDataNames.forEach((k) => a[k]('test', d, c)); }); + + test('应该支持多种类型 data', () => { + const ds = { + baseURL: 'http://api.com', + }; + + const str = '11'; + const obj = {}; + const buff = new ArrayBuffer(0); + + const testStr = new AxiosDomain(ds, async (config) => { + expect(config.data).toBe(str); + + return {} as AxiosResponse; + }); + const testObj = new AxiosDomain(ds, async (config) => { + expect(config.data).toBe(obj); + + return {} as AxiosResponse; + }); + const testBuff = new AxiosDomain(ds, async (config) => { + expect(config.data).toBe(buff); + + return {} as AxiosResponse; + }); + + requestMethodWithDataNames.forEach((k) => { + testStr[k]('test', str); + testObj[k]('test', obj); + testBuff[k]('test', buff); + }); + }); }); diff --git a/test/core/cancel.test.ts b/test/core/cancel.test.ts index 2b45d2c..21fd554 100644 --- a/test/core/cancel.test.ts +++ b/test/core/cancel.test.ts @@ -7,7 +7,13 @@ import { asyncTimeout, } from 'scripts/test.utils'; import axios from 'src/axios'; -import { Cancel, isCancel, CancelToken, isCancelToken } from '@/core/cancel'; +import { + Cancel, + isCancel, + CancelToken, + isCancelToken, + CancelAction, +} from '@/core/cancel'; describe('src/helpers/cancel.ts', () => { test('应该支持空参数', () => { @@ -31,7 +37,7 @@ describe('src/helpers/cancel.ts', () => { }); test('应该可以取消', () => { - let ca!: () => void; + let ca!: CancelAction; const ct = new CancelToken((a) => (ca = a)); expect(ct.throwIfRequested()).toBeUndefined();