fix: 下载进度/上传进度 类型错误

pull/49/head
zjx0905 2023-04-20 21:49:26 +08:00
parent 1867273460
commit 2d691b69cb
18 changed files with 152 additions and 182 deletions

View File

@ -71,14 +71,14 @@ axios('https://api.com/test', {
},
onDownloadProgress(event) {
const {
// 下载进度
// 下载进度百分比
progress,
// 已经下载的数据长度
totalBytesSent,
// 已经下载的数据长度,单位 Bytes
totalBytesWritten,
// 预期需要下载的数据总长度
totalBytesExpectedToSend,
// 预期需要下载的数据总长度,单位 Bytes
totalBytesExpectedToWrite,
} = event;
},
})

View File

@ -101,13 +101,13 @@ axios('https://api.com/test', {
},
onUploadProgress(event) {
const {
// 上传进度
// 上传进度百分比
progress,
// 已经上传的数据长度
// 已经上传的数据长度,单位 Bytes
totalBytesSent,
// 预期需要上传的数据总长度
// 预期需要上传的数据总长度,单位 Bytes
totalBytesExpectedToSend,
} = event;
},

View File

@ -80,7 +80,6 @@ axios('test');
const {
// 静态对象
// 注意:默认导出的 axios 在 CommonJS 里是以 default 属性的方式存在
// import axios from 'axios-miniprogram' 等于 const axios = require('axios-miniprogram').default
default: axios,
// 取消令牌
@ -125,6 +124,36 @@ const {
::::
不同的模块系统存在一些小差异,`esm` 会自动处理默认导入,但 `cjs` 不会处理默认导入。
```ts
// 默认导入esm 和 cjs 这两种写法是等价关系
import axios from 'axios-miniprogram';
const axios = require('axios-miniprogram').default;
// 别名导入esm 和 cjs 这两种写法是等价关系
import * as axios from 'axios-miniprogram';
const axios = require('axios-miniprogram');
// 具名导入esm 和 cjs 这两种写法是等价关系
import {
default as axios,
CancelToken,
isCancel,
Axios,
isAxiosError,
createAdapter,
} from 'axios-miniprogram';
const {
default: axios,
CancelToken,
isCancel,
Axios,
isAxiosError,
createAdapter,
} = require('axios-miniprogram');
```
## 使用
### `axios(url, config?)`

View File

@ -113,8 +113,14 @@ export function mockAdapterFail(options: MockAdapterOptions = {}) {
return mockAdapterBase('fail', options);
}
export const testEachMethods = test.each([
export const methods = [
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
]);
];
export const testEachMethods = test.each(methods);
export function eachMethods(cb: (k: (typeof methods)[number]) => void) {
methods.forEach(cb);
}

View File

@ -1,7 +1,7 @@
import { isFunction, isPlainObject } from './helpers/isTypes';
import { assert } from './helpers/error';
import {
AxiosProgressCallback,
AxiosProgressEvent,
AxiosRequestFormData,
AxiosRequestHeaders,
} from './core/Axios';
@ -229,8 +229,8 @@ export type AxiosAdapterPlatformTask =
| void
| {
abort?(): void;
onProgressUpdate?(callback: AxiosProgressCallback): void;
offProgressUpdate?(callback: AxiosProgressCallback): void;
onProgressUpdate?(callback: (event: AxiosProgressEvent) => void): void;
offProgressUpdate?(callback: (event: AxiosProgressEvent) => void): void;
};
/**

View File

@ -1,7 +1,7 @@
import { buildURL } from '../helpers/buildURL';
import { isAbsoluteURL } from '../helpers/isAbsoluteURL';
import { combineURL } from '../helpers/combineURL';
import { isFunction, isPromise, isString } from '../helpers/isTypes';
import { isString } from '../helpers/isTypes';
import {
AxiosAdapter,
AxiosAdapterRequestMethod,
@ -102,33 +102,55 @@ export type AxiosRequestData =
export type AxiosResponseData = number | AxiosAdapterResponseData;
/**
*
*
*/
export interface AxiosProgressEvent {
export interface AxiosProgressEvent extends AnyObject {
/**
*
*
*/
progress: number;
}
/**
*
*/
export interface AxiosDownloadProgressEvent extends AxiosProgressEvent {
/**
*
* Bytes
*/
totalBytesWritten: number;
/**
* Bytes
*/
totalBytesExpectedToWrite: number;
}
/**
*
*/
export interface AxiosDownloadProgressCallback {
(event: AxiosDownloadProgressEvent): void;
}
/**
*
*/
export interface AxiosUploadProgressEvent extends AxiosProgressEvent {
/**
* Bytes
*/
totalBytesSent: number;
/**
*
* Bytes
*/
totalBytesExpectedToSend: number;
}
/**
*
*
*/
export interface AxiosProgressCallback {
(
/**
*
*/
event: AxiosProgressEvent,
): void;
export interface AxiosUploadProgressCallback {
(event: AxiosUploadProgressEvent): void;
}
/**
@ -197,15 +219,15 @@ export interface AxiosRequestConfig
/**
*
*/
errorHandler?: (error: unknown) => Promise<void> | void;
/**
*
*/
onUploadProgress?: AxiosProgressCallback;
errorHandler?: (error: unknown) => Promise<AxiosResponse>;
/**
*
*/
onDownloadProgress?: AxiosProgressCallback;
onDownloadProgress?: AxiosUploadProgressCallback;
/**
*
*/
onUploadProgress?: AxiosUploadProgressCallback;
}
/**
@ -321,40 +343,34 @@ export default class Axios extends AxiosDomain {
};
#processRequest(config: AxiosRequestConfig) {
const chain: [
Interceptor<AxiosRequestConfig> | Interceptor<AxiosResponse>,
] = [
{
resolved: dispatchRequest,
},
];
const requestHandler = {
resolved: dispatchRequest,
};
const errorHandler = {
rejected: config.errorHandler,
};
const chain: (
| Partial<Interceptor<AxiosRequestConfig>>
| Partial<Interceptor<AxiosResponse>>
)[] = [];
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,
);
}
// 错误处理
next = next.catch((reason) => {
const { errorHandler } = config;
if (isFunction(errorHandler)) {
const promise = errorHandler(reason);
if (isPromise(promise)) {
return promise.then(() => Promise.reject(reason));
}
}
return Promise.reject(reason);
this.interceptors.request.forEach((requestInterceptor) => {
chain.unshift(requestInterceptor);
});
chain.push(requestHandler);
this.interceptors.response.forEach((responseInterceptor) => {
chain.push(responseInterceptor);
});
chain.push(errorHandler);
return next as Promise<AxiosResponse>;
return chain.reduce(
(next, { resolved, rejected }) =>
next.then(
// @ts-ignore
resolved,
rejected,
),
Promise.resolve(config),
) as Promise<AxiosResponse>;
}
}

View File

@ -6,12 +6,7 @@ import {
AxiosAdapterResponseError,
AxiosAdapterPlatformTask,
} from '../adapter';
import {
AxiosProgressCallback,
AxiosRequestConfig,
AxiosResponse,
AxiosResponseError,
} from './Axios';
import { AxiosRequestConfig, AxiosResponse, AxiosResponseError } from './Axios';
import { isCancelToken } from './cancel';
import { AxiosErrorResponse, createError } from './createError';
import { generateType } from './generateType';
@ -103,7 +98,7 @@ export function request(config: AxiosRequestConfig) {
function tryToggleProgressUpdate(
adapterConfig: AxiosAdapterRequestConfig,
adapterProgress?: (callback: AxiosProgressCallback) => void,
adapterProgress?: (cb: (event: AnyObject) => void) => void,
) {
const { onUploadProgress, onDownloadProgress } = adapterConfig;
if (isFunction(adapterProgress)) {

View File

@ -30,10 +30,3 @@ export function isDate(date: any): date is Date {
export function isFunction<T extends Function>(value: any): value is T {
return typeof value === 'function';
}
export function isPromise<T = unknown>(value: any): value is Promise<T> {
return (
_toString.call(value) === '[object Promise]' ||
(isPlainObject(value) && isFunction(value.then))
);
}

View File

@ -9,8 +9,10 @@ export type {
AxiosResponse,
AxiosResponseData,
AxiosResponseError,
AxiosProgressEvent,
AxiosProgressCallback,
AxiosDownloadProgressEvent,
AxiosDownloadProgressCallback,
AxiosUploadProgressEvent,
AxiosUploadProgressCallback,
} from './core/Axios';
export type {
AxiosAdapter,

View File

@ -2,14 +2,10 @@ import { describe, test, expect } from 'vitest';
import Axios from '@/core/Axios';
import { CancelToken, isCancel } from '@/core/cancel';
import { isAxiosError } from '@/core/createError';
import {
requestMethodNames,
requestMethodWithDataNames,
requestMethodWithParamsNames,
} from '@/core/AxiosDomain';
import { createAdapter } from '@/adapter';
import axios from '@/axios';
import defaults from '@/defaults';
import { eachMethods } from 'scripts/test.utils';
describe('src/axios.ts', () => {
test('应该有这些静态属性', () => {
@ -35,11 +31,7 @@ describe('src/axios.ts', () => {
expect(instance.fork).toBeTypeOf('function');
expect(instance.request).toBeTypeOf('function');
[
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
].forEach((k) => {
eachMethods((k) => {
expect(instance[k]).toBeTypeOf('function');
});
});

View File

@ -1,11 +1,7 @@
import { describe, test, expect } from 'vitest';
import {
requestMethodNames,
requestMethodWithDataNames,
requestMethodWithParamsNames,
} from '@/core/AxiosDomain';
import axios from '@/axios';
import defaults from '@/defaults';
import { testEachMethods } from 'scripts/test.utils';
describe('src/axios.ts', () => {
test('应该有这些实例属性及方法', () => {
@ -14,13 +10,9 @@ describe('src/axios.ts', () => {
expect(axios.getUri).toBeTypeOf('function');
expect(axios.fork).toBeTypeOf('function');
expect(axios.request).toBeTypeOf('function');
});
[
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
].forEach((k) => {
expect(axios[k]).toBeTypeOf('function');
});
testEachMethods('%s 应该是一个函数', (k) => {
expect(axios[k]).toBeTypeOf('function');
});
});

View File

@ -3,11 +3,12 @@ import {
mockAdapter,
mockAdapterError,
mockAdapterFail,
testEachMethods,
} from 'scripts/test.utils';
import AxiosDomain, {
requestMethodNames,
requestMethodWithDataNames,
requestMethodWithParamsNames,
requestMethodWithDataNames,
} from '@/core/AxiosDomain';
import Axios from '@/core/Axios';
import axios from '@/axios';
@ -34,14 +35,10 @@ describe('src/core/Axios.ts', () => {
expect(axiosObj.request).toBeTypeOf('function');
expect(axiosObj.getUri).toBeTypeOf('function');
expect(axiosObj.fork).toBeTypeOf('function');
});
[
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
].forEach((k) => {
expect(axiosObj[k]).toBeTypeOf('function');
});
testEachMethods('%s 应该是一个函数', (k) => {
expect(axiosObj[k]).toBeTypeOf('function');
});
test('应该可以发送普通别名请求', () => {
@ -162,6 +159,7 @@ describe('src/core/Axios.ts', () => {
url: 'test',
errorHandler: async (err: unknown) => {
e1(err);
return Promise.reject(err);
},
};
const c2 = {
@ -169,6 +167,7 @@ describe('src/core/Axios.ts', () => {
url: 'test',
errorHandler: async (err: unknown) => {
e2(err);
return Promise.reject(err);
},
};

View File

@ -6,6 +6,7 @@ import AxiosDomain, {
requestMethodWithDataNames,
} from '@/core/AxiosDomain';
import { AxiosResponse } from '@/core/Axios';
import { eachMethods } from 'scripts/test.utils';
describe('src/core/AxiosDomain.ts', () => {
test('应该有这些常量', () => {
@ -23,11 +24,7 @@ describe('src/core/AxiosDomain.ts', () => {
expect(a.defaults).toEqual(c);
expect(a.request).toBeTypeOf('function');
[
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
].forEach((k) => {
eachMethods((k) => {
expect(a[k]).toBeTypeOf('function');
});
});

View File

@ -1,5 +1,5 @@
import { describe, test, expect, vi } from 'vitest';
import { asyncNext, mockAdapter } from 'scripts/test.utils';
import { asyncNext, mockAdapter, testEachMethods } from 'scripts/test.utils';
import { dispatchRequest } from '@/core/dispatchRequest';
import {
requestMethodNames,
@ -38,11 +38,7 @@ describe('src/core/dispatchRequest.ts', () => {
).not.toThrowError();
});
test.each([
...requestMethodNames,
...requestMethodWithDataNames,
...requestMethodWithParamsNames,
])('应该支持 %s 转全大写', (k) => {
testEachMethods('应该支持 %s 转全大写', (k) => {
const c = {
adapter: mockAdapter(),
url: '/',

View File

@ -1,20 +1,11 @@
import { describe, test, expect } from 'vitest';
import { flattenHeaders } from '@/core/flattenHeaders';
import {
requestMethodNames,
requestMethodWithDataNames,
requestMethodWithParamsNames,
} from '@/core/AxiosDomain';
import { eachMethods, methods } from 'scripts/test.utils';
describe('src/core/flattenHeaders.ts', () => {
const keys = [
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
];
const baseHeaders = Object.fromEntries(
keys.map((k) => [k, { v1: `${k}1`, v2: `${k}2` }]),
) as unknown as Record<(typeof keys)[number], AnyObject>;
methods.map((k) => [k, { v1: `${k}1`, v2: `${k}2` }]),
) as unknown as Record<(typeof methods)[number], AnyObject>;
test('应该支持空配置', () => {
expect(flattenHeaders({})).toEqual({});
@ -33,7 +24,7 @@ describe('src/core/flattenHeaders.ts', () => {
const h2 = { v1: 1, v2: 2 };
const h3 = { ...h1, ...h2 };
keys.forEach((a) => {
eachMethods((a) => {
expect(flattenHeaders({ headers: h1, method: a })).toEqual(h1[a]);
expect(flattenHeaders({ headers: h3, method: a })).toEqual(h2);
});
@ -48,14 +39,14 @@ describe('src/core/flattenHeaders.ts', () => {
};
const h2 = { ...baseHeaders, ...h1 };
keys.forEach((a) => {
eachMethods((a) => {
expect(flattenHeaders({ headers: h1, method: a })).toEqual(h1.common);
expect(flattenHeaders({ headers: h2, method: a })).toEqual(h2[a]);
});
});
test.each(
keys.map((k) => [
methods.map((k) => [
k,
{
common: {
@ -83,7 +74,7 @@ describe('src/core/flattenHeaders.ts', () => {
v4: `${k}2`,
};
keys.forEach((a) => {
methods.forEach((a) => {
expect(flattenHeaders({ headers: h, method: a })).toEqual(
a !== k ? h1 : h2,
);

View File

@ -1,20 +1,10 @@
import { describe, test, expect } from 'vitest';
import { generateType } from '@/core/generateType';
import {
requestMethodNames,
requestMethodWithDataNames,
requestMethodWithParamsNames,
} from '@/core/AxiosDomain';
import { testEachMethods } from 'scripts/test.utils';
describe('src/core/generateType.ts', () => {
test('应该是一个 reuqest', () => {
for (const a of [
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
]) {
expect(generateType({ method: a })).toBe('request');
}
testEachMethods('%s 应该是一个 reuqest', (k) => {
expect(generateType({ method: k })).toBe('request');
});
test('应该是一个 upload', () => {

View File

@ -1,17 +1,13 @@
import { describe, test, expect, vi } from 'vitest';
import {
asyncTimeout,
eachMethods,
mockAdapter,
mockAdapterError,
mockAdapterFail,
testEachMethods,
} from 'scripts/test.utils';
import { request } from '@/core/request';
import {
requestMethodNames,
requestMethodWithDataNames,
requestMethodWithParamsNames,
} from '@/core/AxiosDomain';
import axios from '@/axios';
describe('src/core/request.ts', () => {
@ -200,11 +196,7 @@ describe('src/core/request.ts', () => {
download: true,
});
[
...requestMethodNames,
...requestMethodWithParamsNames,
...requestMethodWithDataNames,
].forEach((a) => {
eachMethods((a) => {
request({
adapter: ({ type }) => {
expect(type).toBe('request');

View File

@ -7,7 +7,6 @@ import {
isNull,
isUndefined,
isString,
isPromise,
} from '@/helpers/isTypes';
describe('src/helpers/isTypes.ts', () => {
@ -54,23 +53,4 @@ describe('src/helpers/isTypes.ts', () => {
expect(isString('')).toBeTruthy();
expect(isString(``)).toBeTruthy();
});
test('应该能判断是 Promise', () => {
expect(isPromise({})).toBeFalsy();
expect(isPromise(Promise.resolve())).toBeTruthy();
expect(
isPromise(
new Promise(function () {
return;
}),
),
).toBeTruthy();
expect(
isPromise({
then() {
return;
},
}),
).toBeTruthy();
});
});