feat: 支持复用父级中间件

pull/49/head
zjx0905 2023-04-25 14:28:28 +08:00
parent a84533a09f
commit bfc012b499
5 changed files with 82 additions and 148 deletions

View File

@ -4,9 +4,8 @@ import {
isCancel, isCancel,
} from './request/cancel'; } from './request/cancel';
import { isAxiosError } from './request/createError'; import { isAxiosError } from './request/createError';
import Axios, { AxiosConstructor, AxiosRequestConfig } from './core/Axios'; import Axios, { AxiosConstructor } from './core/Axios';
import { AxiosInstance, createInstance } from './core/createInstance'; import { AxiosInstance, createInstance } from './core/createInstance';
import { mergeConfig } from './core/mergeConfig';
import { createAdapter } from './adpater/createAdapter'; import { createAdapter } from './adpater/createAdapter';
import defaults from './defaults'; import defaults from './defaults';
import { version } from './version'; import { version } from './version';

View File

@ -21,7 +21,10 @@ import {
WITH_DATA_METHODS, WITH_DATA_METHODS,
WITH_PARAMS_METHODS, WITH_PARAMS_METHODS,
} from '../constants/methods'; } from '../constants/methods';
import MiddlewareManager from './MiddlewareManager'; import MiddlewareManager, {
MiddlewareNext,
MiddlewareUse,
} from './MiddlewareManager';
/** /**
* *
@ -379,9 +382,7 @@ export default class Axios {
/** /**
* *
*/ */
#middleware = new MiddlewareManager<AxiosContext>(async (ctx) => { #middleware = new MiddlewareManager<AxiosContext>();
ctx.res = await dispatchRequest(ctx.req);
});
/** /**
* options * options
@ -431,17 +432,12 @@ export default class Axios {
/** /**
* *
*/ */
use: MiddlewareManager<AxiosContext>['use']; use: MiddlewareUse<AxiosContext>;
constructor(defaults: AxiosRequestConfig = {}, parent?: Axios) { constructor(defaults: AxiosRequestConfig = {}, parent?: Axios) {
this.defaults = defaults; this.defaults = defaults;
this.#parent = parent; this.#parent = parent;
if (this.#parent) { this.use = this.#middleware.use;
this.#middleware.flush = this.#parent.#middleware.wrap(
this.#middleware.flush,
);
}
this.use = this.#middleware.use.bind(this.#middleware);
} }
/** /**
@ -463,15 +459,7 @@ export default class Axios {
#processRequest(config: AxiosRequestConfig) { #processRequest(config: AxiosRequestConfig) {
const requestHandler = { const requestHandler = {
resolved: async (config: AxiosRequestConfig) => { resolved: this.#requestHandler,
config.url = combineURL(config.baseURL, config.url);
const ctx: AxiosContext = {
req: config,
res: null,
};
await this.#middleware.flush(ctx);
return ctx.res as AxiosResponse;
},
}; };
const errorHandler = { const errorHandler = {
rejected: config.errorHandler, rejected: config.errorHandler,
@ -481,11 +469,11 @@ export default class Axios {
| Partial<Interceptor<AxiosResponse>> | Partial<Interceptor<AxiosResponse>>
)[] = []; )[] = [];
this.#eacheRequestInterceptors((requestInterceptor) => { this.#eachRequestInterceptors((requestInterceptor) => {
chain.unshift(requestInterceptor); chain.unshift(requestInterceptor);
}); });
chain.push(requestHandler); chain.push(requestHandler);
this.#eacheResponseInterceptors((responseInterceptor) => { this.#eachResponseInterceptors((responseInterceptor) => {
chain.push(responseInterceptor); chain.push(responseInterceptor);
}); });
chain.push(errorHandler); chain.push(errorHandler);
@ -501,19 +489,40 @@ export default class Axios {
) as Promise<AxiosResponse>; ) as Promise<AxiosResponse>;
} }
#eacheRequestInterceptors(executor: InterceptorExecutor<AxiosRequestConfig>) { #eachRequestInterceptors(executor: InterceptorExecutor<AxiosRequestConfig>) {
this.interceptors.request.forEach(executor); this.interceptors.request.forEach(executor);
if (this.#parent) { if (this.#parent) {
this.#parent.#eacheRequestInterceptors(executor); this.#parent.#eachRequestInterceptors(executor);
} }
} }
#eacheResponseInterceptors(executor: InterceptorExecutor<AxiosResponse>) { #eachResponseInterceptors(executor: InterceptorExecutor<AxiosResponse>) {
this.interceptors.response.forEach(executor); this.interceptors.response.forEach(executor);
if (this.#parent) { if (this.#parent) {
this.#parent.#eacheResponseInterceptors(executor); this.#parent.#eachResponseInterceptors(executor);
} }
} }
#requestHandler = async (config: AxiosRequestConfig) => {
config.url = combineURL(config.baseURL, config.url);
const ctx: AxiosContext = {
req: config,
res: null,
};
await this.#flush(ctx, async () => {
ctx.res = await dispatchRequest(ctx.req);
});
return ctx.res as AxiosResponse;
};
#flush(ctx: AxiosContext, finish: MiddlewareNext): Promise<void> {
if (this.#parent) {
return this.#parent.#flush(ctx, () => {
return this.#middleware.flush(ctx, finish);
});
}
return this.#middleware.flush(ctx, finish);
}
} }
for (const method of PLAIN_METHODS) { for (const method of PLAIN_METHODS) {

View File

@ -6,32 +6,39 @@ export interface MiddlewareNext {
(): Promise<void>; (): Promise<void>;
} }
export interface MiddlewareCallback<Conext extends AnyObject> { export interface MiddlewareCallback<Context extends AnyObject> {
(ctx: Conext, next: MiddlewareNext): Promise<void>; (ctx: Context, next: MiddlewareNext): Promise<void>;
} }
export interface MiddlewareFlush<Conext extends AnyObject> { export interface MiddlewareUse<Context extends AnyObject> {
(ctx: Conext): Promise<void>; /**
} *
*
export default class MiddlewareManager<Conext extends AnyObject = AnyObject> { * @param path
#map = new Map<string, MiddlewareCallback<Conext>[]>(); * @param callback
*/
flush: MiddlewareFlush<Conext>; (
constructor(flush: MiddlewareFlush<Conext>) {
this.flush = this.wrap(flush);
}
use(callback: MiddlewareCallback<Conext>): MiddlewareManager<Conext>;
use(
path: string, path: string,
callback: MiddlewareCallback<Conext>, callback: MiddlewareCallback<Context>,
): MiddlewareManager<Conext>; ): MiddlewareManager<Context>;
use( /**
path: string | MiddlewareCallback<Conext>, *
callback?: MiddlewareCallback<Conext>, *
) { * @param callback
*/
(callback: MiddlewareCallback<Context>): MiddlewareManager<Context>;
}
export default class MiddlewareManager<Context extends AnyObject = AnyObject> {
#map = new Map<string, MiddlewareCallback<Context>[]>();
/**
*
*/
use: MiddlewareUse<Context> = (
path: string | MiddlewareCallback<Context>,
callback?: MiddlewareCallback<Context>,
) => {
if (isFunction(path)) { if (isFunction(path)) {
callback = path; callback = path;
path = '/'; path = '/';
@ -45,27 +52,25 @@ export default class MiddlewareManager<Conext extends AnyObject = AnyObject> {
this.#map.set(path, middlewares); this.#map.set(path, middlewares);
return this; return this;
} };
wrap(flush: MiddlewareFlush<Conext>): MiddlewareFlush<Conext> { flush(ctx: Context, finish: MiddlewareNext) {
return (ctx) => { const allMiddlewares: MiddlewareCallback<Context>[] = [];
const allMiddlewares: MiddlewareCallback<Conext>[] = [];
for (const [path, middlewares] of this.#map.entries()) { for (const [path, middlewares] of this.#map.entries()) {
const url = combineURL(ctx.req.baseURL, path); const url = combineURL(ctx.req.baseURL, path);
const checkRE = new RegExp(`^${url}([/?].*)?`); const checkRE = new RegExp(`^${url}([/?].*)?`);
if (path === '/') { if (path === '/') {
allMiddlewares.push(...middlewares); allMiddlewares.push(...middlewares);
} else if (checkRE.test(ctx.req.url!)) { } else if (checkRE.test(ctx.req.url!)) {
allMiddlewares.push(...middlewares); allMiddlewares.push(...middlewares);
}
} }
}
const tasks = [...allMiddlewares, flush]; const tasks = [...allMiddlewares, finish];
return (function next(): Promise<void> { return (function next(): Promise<void> {
return tasks.shift()!(ctx, next); return tasks.shift()!(ctx, next);
})(); })();
};
} }
} }

View File

@ -68,7 +68,7 @@ export function createInstance(config: AxiosRequestConfig, parent?: Axios) {
return createInstance(mergeConfig(instance.defaults, config)); return createInstance(mergeConfig(instance.defaults, config));
}; };
instance.extend = function extend(config: AxiosRequestConfig = {}) { instance.extend = function extend(config: AxiosRequestConfig = {}) {
config.url = combineURL(instance.defaults.baseURL, config.url); config.baseURL = combineURL(instance.defaults.baseURL, config.baseURL);
return createInstance(mergeConfig(instance.defaults, config), context); return createInstance(mergeConfig(instance.defaults, config), context);
}; };
instance.fork = instance.extend; instance.fork = instance.extend;

View File

@ -1,79 +0,0 @@
import { describe, test, expect, vi } from 'vitest';
import MiddlewareManager from '@/core/MiddlewareManager';
describe('src/core/MiddlewareManager.ts', () => {
test('应该有这些实例属性', () => {
const m = new MiddlewareManager(vi.fn());
expect(m.use).toBeTypeOf('function');
expect(m.wrap).toBeTypeOf('function');
});
test('应该可以添加中间件回调', async () => {
const flush = vi.fn(async (ctx) => {
expect(ctx.req.url).toBe('test');
ctx.res = res;
});
const m = new MiddlewareManager(flush);
const ctx = {
req: { url: 'https://api.com' },
res: null,
};
const res = {
'src/core/MiddlewareManager.ts': true,
};
const midde = vi.fn(async (ctx, next) => {
expect(ctx).toBe(ctx);
ctx.req.url = 'test';
await next();
expect(ctx.res).toBe(res);
});
m.use(midde);
await m.flush(ctx);
expect(ctx.res).toBe(res);
expect(midde).toBeCalled();
});
test('应该可以给路径添加中间件回调', async () => {
const flush = vi.fn(async (ctx) => {
ctx.res = res;
});
const m = new MiddlewareManager(flush);
const ctx1 = {
req: {
baseURL: 'https://api.com',
url: 'https://api.com',
},
res: null,
};
const ctx2 = {
req: {
baseURL: 'https://api.com',
url: 'https://api.com/test',
},
res: null,
};
const res = {
'src/core/MiddlewareManager.ts': true,
};
const midde = vi.fn(async (ctx, next) => {
expect(ctx).toBe(ctx);
await next();
expect(ctx.res).toBe(res);
});
m.use('/test', midde);
await m.flush(ctx1);
expect(ctx1.res).toBe(res);
expect(midde).not.toBeCalled();
m.use('/test', midde);
await m.flush(ctx2);
expect(midde).toBeCalled();
});
});