From 1e5809aee3f1653eced0c2ca351c6e3f8616f719 Mon Sep 17 00:00:00 2001 From: zjx0905 <954270063@qq.com> Date: Tue, 25 Apr 2023 22:18:08 +0800 Subject: [PATCH] =?UTF-8?q?revert:=20=E5=8F=96=E6=B6=88=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=BA=E8=B7=AF=E5=BE=84=E6=B7=BB=E5=8A=A0=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/test.utils.ts | 8 +-- src/core/Axios.ts | 25 +++---- src/core/MiddlewareManager.ts | 73 +++++++------------- src/core/createInstance.ts | 4 +- src/index.ts | 9 +-- test/axios.api.test.ts | 1 - test/axios.instance.test.ts | 21 ++++++ test/core/Axios.test.ts | 63 ++++++++++++++--- test/core/MiddlewareManager.test.ts | 101 ++++++++++++++++++++++++++++ test/core/createInstance.test.ts | 91 +++++++++++++++++++++++++ 10 files changed, 315 insertions(+), 81 deletions(-) create mode 100644 test/core/MiddlewareManager.test.ts create mode 100644 test/core/createInstance.test.ts diff --git a/scripts/test.utils.ts b/scripts/test.utils.ts index d5bed1d..ba8bdb4 100644 --- a/scripts/test.utils.ts +++ b/scripts/test.utils.ts @@ -35,10 +35,10 @@ export function noop() { } export function mockResponse( - status: number, - statusText: string, - headers: AnyObject, - data: AnyObject, + status = 200, + statusText = 'OK', + headers: AnyObject = {}, + data: AnyObject = {}, ) { return { status, diff --git a/src/core/Axios.ts b/src/core/Axios.ts index 915bf63..00c9618 100644 --- a/src/core/Axios.ts +++ b/src/core/Axios.ts @@ -22,6 +22,7 @@ import { WITH_PARAMS_METHODS, } from '../constants/methods'; import MiddlewareManager, { + MiddlewareContext, MiddlewareNext, MiddlewareUse, } from './MiddlewareManager'; @@ -306,11 +307,6 @@ export interface AxiosResponseError extends AnyObject { request?: AxiosAdapterPlatformTask; } -export interface AxiosContext { - req: AxiosRequestConfig; - res: null | AxiosResponse; -} - export interface AxiosRequest { (config: AxiosRequestConfig): Promise< AxiosResponse @@ -382,7 +378,7 @@ export default class Axios { /** * 中间件 */ - #middleware = new MiddlewareManager(); + #middleware = new MiddlewareManager(); /** * 发送 options 请求 @@ -432,9 +428,9 @@ export default class Axios { /** * 添加中间件 */ - use: MiddlewareUse; + use: MiddlewareUse; - constructor(defaults: AxiosRequestConfig = {}, parent?: Axios) { + constructor(defaults: AxiosRequestConfig, parent?: Axios) { this.defaults = defaults; this.#parent = parent; this.use = this.#middleware.use; @@ -504,24 +500,23 @@ export default class Axios { } #requestHandler = async (config: AxiosRequestConfig) => { - config.url = combineURL(config.baseURL, config.url); - const ctx: AxiosContext = { + const ctx: MiddlewareContext = { req: config, res: null, }; - await this.#flush(ctx, async () => { + await this.#wrap(ctx, async () => { ctx.res = await dispatchRequest(ctx.req); }); return ctx.res as AxiosResponse; }; - #flush(ctx: AxiosContext, finish: MiddlewareNext): Promise { + #wrap(ctx: MiddlewareContext, flush: MiddlewareNext): Promise { if (this.#parent) { - return this.#parent.#flush(ctx, () => { - return this.#middleware.flush(ctx, finish); + return this.#parent.#wrap(ctx, () => { + return this.#middleware.wrap(ctx, flush); }); } - return this.#middleware.flush(ctx, finish); + return this.#middleware.wrap(ctx, flush); } } diff --git a/src/core/MiddlewareManager.ts b/src/core/MiddlewareManager.ts index c019f53..3ad2891 100644 --- a/src/core/MiddlewareManager.ts +++ b/src/core/MiddlewareManager.ts @@ -1,76 +1,55 @@ import { assert } from '../helpers/error'; import { combineURL } from '../helpers/combineURL'; import { isFunction, isString } from '../helpers/isTypes'; +import { AxiosRequestConfig, AxiosResponse } from './Axios'; export interface MiddlewareNext { (): Promise; } -export interface MiddlewareCallback { - (ctx: Context, next: MiddlewareNext): Promise; +export interface MiddlewareContext { + req: AxiosRequestConfig; + res: null | AxiosResponse; } -export interface MiddlewareUse { - /** - * 添加中间件 - * - * @param path 中间件路径 - * @param callback 中间件回调 - */ - ( - path: string, - callback: MiddlewareCallback, - ): MiddlewareManager; +export interface MiddlewareCallback { + (ctx: MiddlewareContext, next: MiddlewareNext): Promise; +} + +export interface MiddlewareUse { /** * 添加中间件 * * @param callback 中间件回调 */ - (callback: MiddlewareCallback): MiddlewareManager; + (callback: MiddlewareCallback): MiddlewareManager; } -export default class MiddlewareManager { - #map = new Map[]>(); +export default class MiddlewareManager { + /** + * 中间件 + */ + #middlewares: MiddlewareCallback[] = []; /** * 添加中间件 */ - use: MiddlewareUse = ( - path: string | MiddlewareCallback, - callback?: MiddlewareCallback, - ) => { - if (isFunction(path)) { - callback = path; - path = '/'; - } - assert(isString(path), 'path 不是一个 string'); - assert(!!path, 'path 不是一个长度大于零的 string'); + use: MiddlewareUse = (callback: MiddlewareCallback) => { assert(isFunction(callback), 'callback 不是一个 function'); - - const middlewares = this.#map.get(path) ?? []; - middlewares.push(callback!); - this.#map.set(path, middlewares); - + this.#middlewares.push(callback!); return this; }; - flush(ctx: Context, finish: MiddlewareNext) { - const allMiddlewares: MiddlewareCallback[] = []; - - for (const [path, middlewares] of this.#map.entries()) { - const url = combineURL(ctx.req.baseURL, path); - const checkRE = new RegExp(`^${url}([/?].*)?`); - - if (path === '/') { - allMiddlewares.push(...middlewares); - } else if (checkRE.test(ctx.req.url!)) { - allMiddlewares.push(...middlewares); - } - } - - const tasks = [...allMiddlewares, finish]; + /** + * 包装器 + * + * @param ctx 中间件上下文 + * @param flush 目标函数 + */ + wrap(ctx: MiddlewareContext, flush: MiddlewareNext) { + const runners = [...this.#middlewares, flush]; return (function next(): Promise { - return tasks.shift()!(ctx, next); + return runners.shift()!(ctx, next); })(); } } diff --git a/src/core/createInstance.ts b/src/core/createInstance.ts index a398065..ff5574a 100644 --- a/src/core/createInstance.ts +++ b/src/core/createInstance.ts @@ -57,7 +57,7 @@ export function createInstance(config: AxiosRequestConfig, parent?: Axios) { const context = new Axios(config, parent); const instance = context.request as AxiosInstance; - instance.getUri = function getUri(config: AxiosRequestConfig) { + instance.getUri = function getUri(config) { const { url, params, paramsSerializer } = mergeConfig( instance.defaults, config, @@ -67,7 +67,7 @@ export function createInstance(config: AxiosRequestConfig, parent?: Axios) { instance.create = function create(config) { return createInstance(mergeConfig(instance.defaults, config)); }; - instance.extend = function extend(config: AxiosRequestConfig = {}) { + instance.extend = function extend(config) { config.baseURL = combineURL(instance.defaults.baseURL, config.baseURL); return createInstance(mergeConfig(instance.defaults, config), context); }; diff --git a/src/index.ts b/src/index.ts index 3331e82..ea584a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,10 @@ import axios from './axios'; +export type { + MiddlewareContext, + MiddlewareCallback, + MiddlewareNext, +} from './core/MiddlewareManager'; export type { AxiosRequestConfig, AxiosRequestData, @@ -14,10 +19,6 @@ export type { AxiosUploadProgressEvent, AxiosUploadProgressCallback, } from './core/Axios'; -export type { - MiddlewareCallback, - MiddlewareNext, -} from './core/MiddlewareManager'; export type { AxiosInstanceDefaults, AxiosInstance, diff --git a/test/axios.api.test.ts b/test/axios.api.test.ts index 1b3bb57..0b4d731 100644 --- a/test/axios.api.test.ts +++ b/test/axios.api.test.ts @@ -11,7 +11,6 @@ describe('src/axios.ts', () => { test('应该有这些静态属性', () => { expect(axios.Axios).toBe(Axios); expect(axios.CancelToken).toBe(CancelToken); - expect(axios.create).toBeTypeOf('function'); expect(axios.createAdapter).toBe(createAdapter); expect(axios.isCancel).toBe(isCancel); expect(axios.isAxiosError).toBe(isAxiosError); diff --git a/test/axios.instance.test.ts b/test/axios.instance.test.ts index 0badb6b..27d9133 100644 --- a/test/axios.instance.test.ts +++ b/test/axios.instance.test.ts @@ -7,6 +7,7 @@ import { } from '@/constants/methods'; import defaults from '@/defaults'; import axios from '@/axios'; +import { createInstance } from '@/core/createInstance'; describe('src/axios.ts', () => { const data = { result: null }; @@ -15,6 +16,9 @@ describe('src/axios.ts', () => { expect(axios.defaults).toBe(defaults); expect(axios.interceptors).toBeTypeOf('object'); expect(axios.getUri).toBeTypeOf('function'); + expect(axios.create).toBeTypeOf('function'); + expect(axios.extend).toBeTypeOf('function'); + expect(axios.use).toBeTypeOf('function'); expect(axios.fork).toBeTypeOf('function'); expect(axios.request).toBeTypeOf('function'); }); @@ -162,4 +166,21 @@ describe('src/axios.ts', () => { 'test?id=1', ); }); + + test('应该支持中间件', async () => { + const axios = createInstance(defaults); + axios.defaults.adapter = mockAdapter(); + + const cb = vi.fn(async (ctx, next) => { + expect(ctx.res).toBeNull(); + await next(); + expect(ctx.res).toBeTypeOf('object'); + }); + + axios.use(cb); + + await axios.get('test'); + + expect(cb).toBeCalled(); + }); }); diff --git a/test/core/Axios.test.ts b/test/core/Axios.test.ts index 852870a..a27e6e4 100644 --- a/test/core/Axios.test.ts +++ b/test/core/Axios.test.ts @@ -29,6 +29,7 @@ describe('src/core/Axios.ts', () => { expect(axiosObj.defaults).toEqual(c); expect(axiosObj.interceptors).toBeTypeOf('object'); expect(axiosObj.request).toBeTypeOf('function'); + expect(axiosObj.use).toBeTypeOf('function'); }); testEachMethods('%s 应该是一个函数', (k) => { @@ -36,23 +37,34 @@ describe('src/core/Axios.ts', () => { }); test('应该可以发送普通别名请求', () => { - const c = { + const axiosObj = new Axios({ adapter: mockAdapter({ - before: (config) => { - expect(config.url).toBe('http://api.com/test'); - }, data, }), + }); + const c = { + baseURL: 'https://api.com', }; PLAIN_METHODS.forEach((a) => { + axiosObj[a]('test').then((res) => { + expect(res.config.baseURL).toBeUndefined(); + expect(res.data).toEqual(data); + }); axiosObj[a]('test', c).then((res) => { + expect(res.config.baseURL).toBe('https://api.com'); expect(res.data).toEqual(data); }); }); }); test('应该可以发送带参数的别名请求', () => { + const axiosObj = new Axios({ + adapter: mockAdapter({ + data, + }), + baseURL: 'http://api.com', + }); const p = { id: 1 }; const c1 = { adapter: mockAdapter({ @@ -74,6 +86,12 @@ describe('src/core/Axios.ts', () => { }; WITH_PARAMS_METHODS.forEach((a) => { + axiosObj[a]('test').then((res) => { + expect(res.data).toEqual(data); + }); + axiosObj[a]('test', p).then((res) => { + expect(res.data).toEqual(data); + }); axiosObj[a]('test', p, c1).then((res) => { expect(res.data).toEqual(data); }); @@ -84,6 +102,12 @@ describe('src/core/Axios.ts', () => { }); test('应该可以发送带数据的别名请求', () => { + const axiosObj = new Axios({ + adapter: mockAdapter({ + data, + }), + baseURL: 'http://api.com', + }); const d = { id: 1 }; const c1 = { adapter: mockAdapter({ @@ -105,6 +129,12 @@ describe('src/core/Axios.ts', () => { }; WITH_DATA_METHODS.forEach((a) => { + axiosObj[a]('test').then((res) => { + expect(res.data).toEqual(data); + }); + axiosObj[a]('test', d).then((res) => { + expect(res.data).toEqual(data); + }); axiosObj[a]('test', d, c1).then((res) => { expect(res.data).toEqual(data); }); @@ -207,7 +237,7 @@ describe('src/core/Axios.ts', () => { }); test('添加多个请求拦截器时应该按添加顺序从后往前依次执行', () => { - const axiosObj = new Axios(); + const axiosObj = new Axios({}); const cb1 = vi.fn((v) => { expect(v.params.index).toBe(2); @@ -240,7 +270,7 @@ describe('src/core/Axios.ts', () => { }); test('请求拦截器应该支持抛出异常', async () => { - const axiosObj = new Axios(); + const axiosObj = new Axios({}); const c = { adapter: vi.fn(), url: 'test' }; const body = (v: any) => { throw { ...v, throw: true }; @@ -293,7 +323,7 @@ describe('src/core/Axios.ts', () => { }); test('添加多个响应拦截器时应该按添加顺序从前往后依次执行', async () => { - const axiosObj = new Axios(); + const axiosObj = new Axios({}); const cb1 = vi.fn((v) => { expect(v.data.index).toBe(0); @@ -323,7 +353,7 @@ describe('src/core/Axios.ts', () => { }); test('响应拦截器应该支持抛出异常', async () => { - const axiosObj = new Axios(); + const axiosObj = new Axios({}); const c = { adapter: vi.fn(mockAdapter()), url: 'test' }; const body = () => { throw { throw: true }; @@ -347,4 +377,21 @@ describe('src/core/Axios.ts', () => { expect(res2).not.toBeCalled(); expect(rej2).toBeCalled(); }); + + test('应该支持中间件', async () => { + const axiosObj = new Axios({ + adapter: mockAdapter(), + }); + const cb = vi.fn(async (ctx, next) => { + expect(ctx.res).toBeNull(); + await next(); + expect(ctx.res).toBeTypeOf('object'); + }); + + axiosObj.use(cb); + + await axiosObj.get('test'); + + expect(cb).toBeCalled(); + }); }); diff --git a/test/core/MiddlewareManager.test.ts b/test/core/MiddlewareManager.test.ts new file mode 100644 index 0000000..6bcc034 --- /dev/null +++ b/test/core/MiddlewareManager.test.ts @@ -0,0 +1,101 @@ +import { describe, test, expect, vi } from 'vitest'; +import MiddlewareManager, { MiddlewareContext } from '@/core/MiddlewareManager'; +import { AxiosResponse } from '@/index'; + +describe('src/core/MiddlewareManager.ts', () => { + test('应该有这些实例属性', () => { + const m = new MiddlewareManager(); + + expect(m.use).toBeTypeOf('function'); + expect(m.wrap).toBeTypeOf('function'); + }); + + test('应该抛出异常', () => { + const m = new MiddlewareManager(); + + expect(() => m.use(undefined as any)).toThrowError( + '[axios-miniprogram]: callback 不是一个 function', + ); + }); + + test('应该支持添加中间件', () => { + const m = new MiddlewareManager(); + const c: MiddlewareContext = { + req: {}, + res: null, + }; + const res = {} as AxiosResponse; + + const cb = vi.fn(async (ctx, next) => { + await next(); + }); + const flush = vi.fn(async () => { + c.res = res; + }); + + m.use(cb); + m.wrap(c, flush); + + expect(cb).toBeCalled(); + expect(flush).toBeCalled(); + }); + + test('应该支持洋葱模型', async () => { + const m = new MiddlewareManager(); + const c: MiddlewareContext = { + req: {}, + res: null, + }; + const res = {} as AxiosResponse; + + const cb1 = vi.fn(async (ctx, next) => { + // 1 + expect(ctx.req.step).toBeUndefined(); + ctx.req.step = 'cb1 start'; + await next(); + // 8 + expect(ctx.res.step).toBe('cb2 end'); + ctx.res.step = 'cb1 end'; + }); + const cb2 = vi.fn((ctx, next) => { + // 2 + expect(ctx.req.step).toBe('cb1 start'); + ctx.req.step = 'cb2 start'; + return next().then(() => { + // 7 + expect(ctx.res.step).toBe('cb3 end'); + ctx.res.step = 'cb2 end'; + }); + }); + const cb3 = vi.fn(async (ctx, next) => { + // 3 + expect(ctx.req.step).toBe('cb2 start'); + ctx.req.step = 'cb3 start'; + await next(); + // 6 + expect(ctx.res.step).toBe('flush end'); + ctx.res.step = 'cb3 end'; + }); + const flush = vi.fn(async () => { + // 4 + expect(c.req.step).toBe('cb3 start'); + c.req.step = 'flush start'; + c.res = res; + // 5 + expect(c.res.step).toBeUndefined(); + c.res.step = 'flush end'; + }); + + m.use(cb1); + m.use(cb2); + m.use(cb3); + await m.wrap(c, flush); + + expect(c.req.step).toBe('flush start'); + expect(c.res!.step).toBe('cb1 end'); + expect(cb1).toBeCalled(); + expect(cb2).toBeCalled(); + expect(cb3).toBeCalled(); + expect(flush).toBeCalled(); + }); +}); diff --git a/test/core/createInstance.test.ts b/test/core/createInstance.test.ts new file mode 100644 index 0000000..b29dbbf --- /dev/null +++ b/test/core/createInstance.test.ts @@ -0,0 +1,91 @@ +import { describe, test, expect, vi } from 'vitest'; +import { eachMethods, mockAdapter } from 'scripts/test.utils'; +import { createInstance } from '@/core/createInstance'; + +describe('src/core/createInstance.ts', () => { + test('应该可以创建实例', () => { + const i = createInstance({}); + + expect(i).toBeTypeOf('function'); + expect(i.defaults).toBeTypeOf('object'); + expect(i.interceptors).toBeTypeOf('object'); + expect(i.getUri).toBeTypeOf('function'); + expect(i.create).toBeTypeOf('function'); + expect(i.extend).toBeTypeOf('function'); + expect(i.fork).toBeTypeOf('function'); + expect(i.use).toBeTypeOf('function'); + expect(i.request).toBeTypeOf('function'); + + eachMethods((k) => { + expect(i[k]).toBeTypeOf('function'); + }); + }); + + test('应该支持合并配置', () => { + const parent = createInstance({}); + const child = parent.create({ + baseURL: 'https://api.com', + }); + + expect(parent.defaults.baseURL).toBeUndefined(); + expect(child.defaults.baseURL).toBe('https://api.com'); + }); + + test('应该支持复用拦截器', async () => { + const parent = createInstance({ + baseURL: 'https://api.com', + }); + const child = parent.extend({ + adapter: mockAdapter(), + }); + const cb1 = vi.fn((config) => config); + const cb2 = vi.fn((response) => response); + const cb3 = vi.fn((config) => config); + const cb4 = vi.fn((response) => response); + + parent.interceptors.request.use(cb1); + parent.interceptors.response.use(cb2); + child.interceptors.request.use(cb3); + child.interceptors.response.use(cb4); + + await child('test'); + + expect(cb1).toBeCalled(); + expect(cb2).toBeCalled(); + expect(cb3).toBeCalled(); + expect(cb4).toBeCalled(); + }); + + test('应该支持复用中间件', async () => { + const parent = createInstance({ + baseURL: 'https://api.com', + }); + const child = parent.extend({ + adapter: mockAdapter(), + }); + const cb1 = vi.fn(async (ctx, next) => { + await next(); + }); + const cb2 = vi.fn(async (ctx, next) => { + await next(); + }); + const cb3 = vi.fn(async (ctx, next) => { + await next(); + }); + const cb4 = vi.fn(async (ctx, next) => { + await next(); + }); + + parent.use(cb1); + parent.use(cb2); + child.use(cb3); + child.use(cb4); + + await child('test'); + + expect(cb1).toBeCalled(); + expect(cb2).toBeCalled(); + expect(cb3).toBeCalled(); + expect(cb4).toBeCalled(); + }); +});