feat: 支持清空拦截器

pull/49/head
zjx0905 2023-04-17 19:27:44 +08:00
parent 09ac2a94e2
commit cbcc43ad77
10 changed files with 105 additions and 91 deletions

View File

@ -9,6 +9,7 @@
"root": true,
"rules": {
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-non-null-assertion": 0
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/ban-ts-comment": 0
}
}

View File

@ -259,7 +259,7 @@ axios.defaults.adapter = axios.createAdapter({
});
```
可以使用 `createAdapter` 彻底抹平存在差异的部分,实现全平台完美适配
可以进一步抹平存在差异的部分,实现完美适配全平台
```ts
import axios from 'axios-miniprogram';

View File

@ -337,7 +337,7 @@ export function createAdapter(platform: AxiosPlatform) {
return adapter;
}
export function isPlatform(value: unknown): value is AxiosPlatform {
export function isPlatform(value: any): value is AxiosPlatform {
return (
isPlainObject(value) &&
isFunction(value.request) &&

View File

@ -57,13 +57,8 @@ function createInstance(defaults: AxiosRequestConfig) {
const context = new Axios(defaults);
const instance = context.request as AxiosInstance;
Object.assign(instance, context, {
// instance.fork 内部调用了 context 的私有方法
// 所以直接调用 instance.fork 会导致程序抛出无法访问 context 私有方法的异常
// instance.fork 调用时 this 重新指向 context解决此问题
fork: context.fork.bind(context),
});
Object.setPrototypeOf(instance, Object.getPrototypeOf(context));
Object.assign(instance, context);
Object.setPrototypeOf(instance, Axios.prototype);
return instance;
}

View File

@ -11,12 +11,13 @@ import {
AxiosAdapterResponseError,
AxiosAdapterResponseData,
} from '../adapter';
import InterceptorManager, { Interceptor } from './InterceptorManager';
import { mergeConfig } from './mergeConfig';
import { CancelToken } from './cancel';
import { dispatchRequest } from './dispatchRequest';
import { AxiosTransformer } from './transformData';
import AxiosDomain from './AxiosDomain';
import InterceptorManager from './InterceptorManager';
export type AxiosRequestMethod =
| AxiosAdapterRequestMethod
@ -243,26 +244,36 @@ export default class Axios extends AxiosDomain {
/**
*
*/
fork(defaults: AxiosRequestConfig = {}) {
fork = (defaults: AxiosRequestConfig = {}) => {
if (isString(defaults.baseURL) && !isAbsoluteURL(defaults.baseURL)) {
defaults.baseURL = combineURL(this.defaults.baseURL, defaults.baseURL);
}
return new AxiosDomain(mergeConfig(this.defaults, defaults), (config) =>
this.#processRequest(config),
);
}
};
#processRequest(config: AxiosRequestConfig) {
const { request, response } = this.interceptors;
const chain: [
Interceptor<AxiosRequestConfig> | Interceptor<AxiosResponse>,
] = [
{
resolved: dispatchRequest,
},
];
let promiseRequest = Promise.resolve(config);
request.forEach(({ resolved, rejected }) => {
promiseRequest = promiseRequest.then(resolved, rejected);
}, true);
let promiseResponse = promiseRequest.then(dispatchRequest);
response.forEach(({ resolved, rejected }) => {
promiseResponse = promiseResponse.then(resolved, rejected);
});
return promiseResponse;
this.interceptors.request.forEach(chain.unshift.bind(chain));
this.interceptors.response.forEach(chain.push.bind(chain));
let next = Promise.resolve(config);
for (const { resolved, rejected } of chain) {
next = next.then(
// @ts-ignore
resolved,
rejected,
);
}
return next as Promise<AxiosResponse>;
}
}

View File

@ -167,38 +167,36 @@ export default class AxiosDomain {
return processRequest(mergeConfig(this.defaults, config));
};
this.#createAsRequests();
this.#createAspRequests();
this.#createAsdRequests();
}
#createAsRequests() {
for (const alias of AxiosDomain.as) {
this[alias] = function processAsRequest(url, config = {}) {
config.method = alias;
return this.request(url, config);
};
}
}
#createAspRequests() {
for (const alias of AxiosDomain.asp) {
this[alias] = function processAspRequest(url, params = {}, config = {}) {
config.method = alias;
config.params = deepMerge(params, config.params ?? {});
return this.request(url, config);
};
}
}
#createAsdRequests() {
for (const alias of AxiosDomain.asd) {
this[alias] = function processAsdRequest(url, data = {}, config = {}) {
config.method = alias;
config.data = deepMerge(data, config.data ?? {});
return this.request(url, config);
};
}
}
}
for (const alias of AxiosDomain.as) {
AxiosDomain.prototype[alias] = function processAsRequest(url, config = {}) {
config.method = alias;
return this.request(url, config);
};
}
for (const alias of AxiosDomain.asp) {
AxiosDomain.prototype[alias] = function processAspRequest(
url,
params = {},
config = {},
) {
config.method = alias;
config.params = deepMerge(params, config.params ?? {});
return this.request(url, config);
};
}
for (const alias of AxiosDomain.asd) {
AxiosDomain.prototype[alias] = function processAsdRequest(
url,
data = {},
config = {},
) {
config.method = alias;
config.data = deepMerge(data, config.data ?? {});
return this.request(url, config);
};
}

View File

@ -18,27 +18,33 @@ export interface InterceptorExecutor<T = unknown> {
export default class InterceptorManager<T = unknown> {
#id = 0;
#interceptors: AnyObject<Interceptor<T>> = {};
#interceptors = new Map<number, Interceptor<T>>();
get size() {
return this.#interceptors.size;
}
use(
resolved: InterceptorResolved<T>,
rejected?: InterceptorRejected<T>,
): number {
this.#interceptors[++this.#id] = {
this.#interceptors.set(++this.#id, {
resolved,
rejected,
};
});
return this.#id;
}
eject(id: number): void {
delete this.#interceptors[id];
eject(id: number): boolean {
return this.#interceptors.delete(id);
}
forEach(executor: InterceptorExecutor<T>, reverse?: boolean): void {
let interceptors: Interceptor<T>[] = Object.values(this.#interceptors);
if (reverse) interceptors = interceptors.reverse();
interceptors.forEach(executor);
clear() {
this.#interceptors.clear();
}
forEach(executor: InterceptorExecutor<T>): void {
this.#interceptors.forEach(executor);
}
}

View File

@ -26,8 +26,8 @@ export function mergeConfig(
);
for (const key of keysSet) {
const val1 = config1[key] as any;
const val2 = config2[key] as any;
const val1 = config1[key];
const val2 = config2[key];
// 只从 config2 中取值
if (fromConfig2Map[key]) {

View File

@ -14,7 +14,7 @@ export function isString(value: any): value is string {
);
}
export function isPlainObject(value: any): value is object & AnyObject {
export function isPlainObject<T extends AnyObject>(value: any): value is T {
return _toString.call(value) === '[object Object]';
}

View File

@ -16,8 +16,12 @@ describe('src/core/InterceptorManager.ts', () => {
const rej = vi.fn();
const cb = vi.fn();
expect(i.size).toBe(0);
const id = i.use(res, rej);
expect(i.size).toBe(1);
i.forEach(({ resolved, rejected }) => {
expect(resolved).toBe(res);
expect(rejected).toBe(rej);
@ -26,10 +30,31 @@ describe('src/core/InterceptorManager.ts', () => {
i.eject(id);
i.forEach(cb);
expect(i.size).toBe(0);
expect(cb).not.toBeCalled();
});
test('应该可以依次执行拦截处理函数', () => {
test('应该可以清理所有拦截处理函数', () => {
const i = new InterceptorManager();
const res = vi.fn();
const rej = vi.fn();
const cb = vi.fn();
expect(i.size).toBe(0);
i.use(res, rej);
i.use(res, rej);
i.use(res, rej);
expect(i.size).toBe(3);
i.clear();
expect(i.size).toBe(0);
});
test('应该可以调用 forEach', () => {
const i = new InterceptorManager();
const res1 = vi.fn();
const rej1 = vi.fn();
@ -50,26 +75,4 @@ describe('src/core/InterceptorManager.ts', () => {
rejected: rej2,
});
});
test('应该可以反向依次执行拦截处理函数', () => {
const i = new InterceptorManager();
const res1 = vi.fn();
const rej1 = vi.fn();
const res2 = vi.fn();
const rej2 = vi.fn();
const cb = vi.fn();
i.use(res1, rej1);
i.use(res2, rej2);
i.forEach(cb, true);
expect(cb.mock.calls[0][0]).toEqual({
resolved: res2,
rejected: rej2,
});
expect(cb.mock.calls[1][0]).toEqual({
resolved: res1,
rejected: rej1,
});
});
});