feat: upload相关全部统一化,并且完善了mp端的上传逻辑

This commit is contained in:
Pan Qiancheng 2025-12-26 17:03:26 +08:00
parent f16b3f91a8
commit fff9750b2a
25 changed files with 443 additions and 178 deletions

30
es/types/Upload.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
type UploadFileFn = (options: {
file: string | File | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
isFilePath?: boolean;
}) => Promise<{
status: number;
statusText: string;
statusCode?: number;
headers: {
get(name: string): string | null;
};
json(): Promise<any>;
text(): Promise<string>;
errMsg?: string;
data?: any;
}>;
export interface UploadInterface {
uploadFile: UploadFileFn;
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
getActiveUploads(): string[];
}
export {};

1
es/types/Upload.js Normal file
View File

@ -0,0 +1 @@
export {};

16
es/utils/upload.d.ts vendored
View File

@ -1,6 +1,16 @@
export declare class Upload {
uploadFile(file: string | File | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
import { UploadInterface } from "../types/Upload";
export declare class Upload implements UploadInterface {
uploadFile(options: {
file: string | File | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
isFilePath?: boolean;
}): Promise<any>;
private controllers;
constructor();
abortUpload(uploadId: string): boolean;

View File

@ -1,6 +1,5 @@
export class Upload {
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
async uploadFile(options) {
console.warn('server不会调用此函数');
}
controllers = new Map();

View File

@ -1,4 +1,18 @@
export declare class Upload {
uploadFile(file: string | File | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
import { UploadInterface } from '../types/Upload';
export declare class Upload implements UploadInterface {
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
getActiveUploads(): string[];
uploadFile(options: {
file: string | File | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
isFilePath?: boolean;
}): Promise<any>;
}

View File

@ -1,41 +1,47 @@
import { promisify } from './promisify';
export class Upload {
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
abortUpload(uploadId) {
return false;
}
abortAllUploads() {
}
getUploadStatus(uploadId) {
return 'not-found';
}
getActiveUploads() {
return [];
}
async uploadFile(options) {
const { file, name, uploadUrl, formData, isFilePath, method = "POST" } = options;
const isPut = method === "PUT";
if (isPut) {
return new Promise((resolve, reject) => {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: file,
success: (fileRes) => {
// 使用 PUT 方法上传
wx.request({
url: uploadUrl,
method: 'PUT',
data: fileRes.data, // ArrayBuffer 格式
header: {
'Content-Type': 'image/jpeg', // 根据实际文件类型设置
},
success: (uploadRes) => {
if (uploadRes.statusCode === 200) {
resolve(uploadRes);
}
else {
reject(new Error(`HTTP Error: ${uploadRes.statusCode}`));
}
},
fail: (err) => {
console.error('上传失败', err);
reject(err);
}
});
},
fail: (err) => {
console.error('读取文件失败', err);
reject(err);
}
if (isFilePath) {
return new Promise((resolve, reject) => {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: file,
encoding: 'binary',
success: res => {
resolve(global.fetch(uploadUrl, {
method: "PUT",
headers: {
'Content-Type': 'application/octet-stream',
},
body: res.data,
}));
},
fail: err => {
reject(err);
}
});
});
}
return global.fetch(uploadUrl, {
method: "PUT",
headers: {
'Content-Type': 'application/octet-stream',
},
body: file,
});
}
else {

View File

@ -1,4 +1,17 @@
export declare class Upload {
uploadFile(file: File | string | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
import { UploadInterface } from "../types/Upload";
export declare class Upload implements UploadInterface {
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
getActiveUploads(): string[];
uploadFile(options: {
file: File | string | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
}): Promise<any>;
}

View File

@ -1,6 +1,17 @@
export class Upload {
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
abortUpload(uploadId) {
return false;
}
abortAllUploads() {
}
getUploadStatus(uploadId) {
return 'not-found';
}
getActiveUploads() {
return [];
}
async uploadFile(options) {
const { file, name, uploadUrl, formData, method = "POST" } = options;
const isPut = method === "PUT";
if (isPut) {
// S3 预签名上传

View File

@ -1,8 +1,17 @@
export declare class Upload {
import { UploadInterface } from "../types/Upload";
export declare class Upload implements UploadInterface {
private controllers;
constructor();
uploadFile(file: File | string | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
uploadFile(options: {
file: File | string | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
}): Promise<any>;
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';

View File

@ -7,8 +7,8 @@ export class Upload {
this.getUploadStatus = this.getUploadStatus.bind(this);
this.getActiveUploads = this.getActiveUploads.bind(this);
}
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
async uploadFile(options) {
const { file, name, uploadUrl, formData, getPercent, uploadId, method = "POST" } = options;
const isPut = method === "PUT";
const id = uploadId || this.generateUploadId(file, uploadUrl);
// 如果已有相同ID的上传在进行先中断它
@ -18,7 +18,6 @@ export class Upload {
// 创建新的 AbortController
const controller = new AbortController();
this.controllers.set(id, controller);
console.log(`Starting upload with ID: ${id}`);
// 进度监听模式
if (getPercent) {
return new Promise((resolve, reject) => {

29
lib/types/Upload.d.ts vendored Normal file
View File

@ -0,0 +1,29 @@
type UploadFileFn = (options: {
file: string | File | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
isFilePath?: boolean;
}) => Promise<{
status: number;
statusText: string;
headers: {
get(name: string): string | null;
};
json(): Promise<any>;
text(): Promise<string>;
errMsg?: string;
data?: any;
}>;
export interface UploadInterface {
uploadFile: UploadFileFn;
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
getActiveUploads(): string[];
}
export {};

2
lib/types/Upload.js Normal file
View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

16
lib/utils/upload.d.ts vendored
View File

@ -1,6 +1,16 @@
export declare class Upload {
uploadFile(file: string | File | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
import { UploadInterface } from "../types/Upload";
export declare class Upload implements UploadInterface {
uploadFile(options: {
file: string | File | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
isFilePath?: boolean;
}): Promise<any>;
private controllers;
constructor();
abortUpload(uploadId: string): boolean;

View File

@ -2,8 +2,7 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.Upload = void 0;
class Upload {
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
async uploadFile(options) {
console.warn('server不会调用此函数');
}
controllers = new Map();

View File

@ -1,4 +1,18 @@
export declare class Upload {
uploadFile(file: string | File | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
import { UploadInterface } from '../types/Upload';
export declare class Upload implements UploadInterface {
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
getActiveUploads(): string[];
uploadFile(options: {
file: string | File | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
isFilePath?: boolean;
}): Promise<any>;
}

View File

@ -3,42 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.Upload = void 0;
const promisify_1 = require("./promisify");
class Upload {
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
abortUpload(uploadId) {
return false;
}
abortAllUploads() {
}
getUploadStatus(uploadId) {
return 'not-found';
}
getActiveUploads() {
return [];
}
async uploadFile(options) {
const { file, name, uploadUrl, formData, isFilePath, method = "POST" } = options;
const isPut = method === "PUT";
if (isPut) {
return new Promise((resolve, reject) => {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: file,
success: (fileRes) => {
// 使用 PUT 方法上传
wx.request({
url: uploadUrl,
method: 'PUT',
data: fileRes.data, // ArrayBuffer 格式
header: {
'Content-Type': 'image/jpeg', // 根据实际文件类型设置
},
success: (uploadRes) => {
if (uploadRes.statusCode === 200) {
resolve(uploadRes);
}
else {
reject(new Error(`HTTP Error: ${uploadRes.statusCode}`));
}
},
fail: (err) => {
console.error('上传失败', err);
reject(err);
}
});
},
fail: (err) => {
console.error('读取文件失败', err);
reject(err);
}
if (isFilePath) {
return new Promise((resolve, reject) => {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: file,
encoding: 'binary',
success: res => {
resolve(global.fetch(uploadUrl, {
method: "PUT",
headers: {
'Content-Type': 'application/octet-stream',
},
body: res.data,
}));
},
fail: err => {
reject(err);
}
});
});
}
return global.fetch(uploadUrl, {
method: "PUT",
headers: {
'Content-Type': 'application/octet-stream',
},
body: file,
});
}
else {

View File

@ -1,4 +1,17 @@
export declare class Upload {
uploadFile(file: File | string | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
import { UploadInterface } from "../types/Upload";
export declare class Upload implements UploadInterface {
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
getActiveUploads(): string[];
uploadFile(options: {
file: File | string | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
}): Promise<any>;
}

View File

@ -2,8 +2,19 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.Upload = void 0;
class Upload {
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
abortUpload(uploadId) {
return false;
}
abortAllUploads() {
}
getUploadStatus(uploadId) {
return 'not-found';
}
getActiveUploads() {
return [];
}
async uploadFile(options) {
const { file, name, uploadUrl, formData, method = "POST" } = options;
const isPut = method === "PUT";
if (isPut) {
// S3 预签名上传

View File

@ -1,8 +1,17 @@
export declare class Upload {
import { UploadInterface } from "../types/Upload";
export declare class Upload implements UploadInterface {
private controllers;
constructor();
uploadFile(file: File | string | Blob, name: string, uploadUrl: string, formData: Record<string, any>, autoInform?: boolean, getPercent?: Function, uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"): Promise<any>;
uploadFile(options: {
file: File | string | Blob;
name: string;
uploadUrl: string;
formData: Record<string, any>;
autoInform?: boolean;
getPercent?: Function;
uploadId?: string;
method?: "POST" | "PUT" | "PATCH";
}): Promise<any>;
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';

View File

@ -10,8 +10,8 @@ class Upload {
this.getUploadStatus = this.getUploadStatus.bind(this);
this.getActiveUploads = this.getActiveUploads.bind(this);
}
async uploadFile(file, name, uploadUrl, formData, autoInform, getPercent, uploadId, // 新增上传任务ID用于中断特定上传
method = "POST") {
async uploadFile(options) {
const { file, name, uploadUrl, formData, getPercent, uploadId, method = "POST" } = options;
const isPut = method === "PUT";
const id = uploadId || this.generateUploadId(file, uploadUrl);
// 如果已有相同ID的上传在进行先中断它
@ -21,7 +21,6 @@ class Upload {
// 创建新的 AbortController
const controller = new AbortController();
this.controllers.set(id, controller);
console.log(`Starting upload with ID: ${id}`);
// 进度监听模式
if (getPercent) {
return new Promise((resolve, reject) => {

39
src/types/Upload.ts Normal file
View File

@ -0,0 +1,39 @@
type UploadFileFn = (options: {
// 需要上传的内容
file: string | File | Blob;
// 表单字段名
name: string;
// 上传地址
uploadUrl: string;
// 额外的表单数据
formData: Record<string, any>;
// 是否自动通知上传结果
autoInform?: boolean;
// 进度回调函数
getPercent?: Function;
// 上传任务ID用于中断特定上传
uploadId?: string;
// HTTP方法
method?: "POST" | "PUT" | "PATCH";
// 是否为文件路径主要用于小程序在PUT模式下需要
isFilePath?: boolean;
}) => Promise<{
status: number;
statusText: string;
statusCode?: number;
headers: {
get(name: string): string | null;
};
json(): Promise<any>;
text(): Promise<string>;
errMsg?: string;
data?: any;
}>;
export interface UploadInterface {
uploadFile: UploadFileFn;
abortUpload(uploadId: string): boolean;
abortAllUploads(): void;
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found';
getActiveUploads(): string[];
}

View File

@ -1,51 +1,67 @@
import { UploadInterface } from '../types/Upload';
import { promisify } from './promisify';
export class Upload {
export class Upload implements UploadInterface {
abortUpload(uploadId: string): boolean {
return false;
}
abortAllUploads(): void {
}
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found' {
return 'not-found';
}
getActiveUploads(): string[] {
return [];
}
async uploadFile(
file: string | File | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method: "POST" | "PUT" | "PATCH" = "POST",
options: {
file: string | File | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"
isFilePath?: boolean
}
): Promise<any> {
const { file, name, uploadUrl, formData, isFilePath, method = "POST" } = options;
const isPut = method === "PUT";
if (isPut) {
return new Promise((resolve, reject) => {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: file as string,
success: (fileRes) => {
// 使用 PUT 方法上传
wx.request({
url: uploadUrl,
method: 'PUT',
data: fileRes.data, // ArrayBuffer 格式
header: {
'Content-Type': 'image/jpeg', // 根据实际文件类型设置
},
success: (uploadRes) => {
if (uploadRes.statusCode === 200) {
resolve(uploadRes);
} else {
reject(new Error(`HTTP Error: ${uploadRes.statusCode}`));
}
},
fail: (err) => {
console.error('上传失败', err);
reject(err);
}
});
},
fail: (err) => {
console.error('读取文件失败', err);
reject(err);
}
if (isFilePath) {
return new Promise((resolve, reject) => {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: file as string,
encoding: 'binary',
success: res => {
resolve(global.fetch(uploadUrl, {
method: "PUT",
headers: {
'Content-Type': 'application/octet-stream',
},
body: res.data,
}));
}
,
fail: err => {
reject(err);
}
});
});
});
}
return global.fetch(uploadUrl, {
method: "PUT",
headers: {
'Content-Type': 'application/octet-stream',
},
body: file as any,
})
} else {

View File

@ -1,16 +1,34 @@
import { UploadInterface } from "../types/Upload";
export class Upload {
export class Upload implements UploadInterface {
abortUpload(uploadId: string): boolean {
return false;
}
abortAllUploads(): void {
}
getUploadStatus(uploadId: string): 'uploading' | 'completed' | 'aborted' | 'not-found' {
return 'not-found';
}
getActiveUploads(): string[] {
return [];
}
async uploadFile(
file: File | string | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method: "POST" | "PUT" | "PATCH" = "POST",
options: {
file: File | string | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"
}
): Promise<any> {
const { file, name, uploadUrl, formData, method = "POST" } = options;
const isPut = method === "PUT";
if (isPut) {

View File

@ -1,14 +1,19 @@
import { UploadInterface } from "../types/Upload";
export class Upload {
export class Upload implements UploadInterface {
async uploadFile(
file: string | File | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method: "POST" | "PUT" | "PATCH" = "POST",
options: {
file: string | File | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH",
isFilePath?: boolean,
}
): Promise<any> {
console.warn('server不会调用此函数')
}

View File

@ -1,6 +1,7 @@
import { OakUserException } from "oak-domain/lib/types";
import { UploadInterface } from "../types/Upload";
export class Upload {
export class Upload implements UploadInterface {
private controllers: Map<string, AbortController> = new Map();
constructor() {
@ -12,15 +13,19 @@ export class Upload {
}
async uploadFile(
file: File | string | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method: "POST" | "PUT" | "PATCH" = "POST",
options: {
file: File | string | Blob,
name: string,
uploadUrl: string,
formData: Record<string, any>,
autoInform?: boolean,
getPercent?: Function,
uploadId?: string, // 新增上传任务ID用于中断特定上传
method?: "POST" | "PUT" | "PATCH"
}
): Promise<any> {
const { file, name, uploadUrl, formData, getPercent, uploadId, method = "POST" } = options;
const isPut = method === "PUT";
const id = uploadId || this.generateUploadId(file, uploadUrl);
@ -33,8 +38,6 @@ export class Upload {
const controller = new AbortController();
this.controllers.set(id, controller);
console.log(`Starting upload with ID: ${id}`);
// 进度监听模式
if (getPercent) {
return new Promise((resolve, reject) => {
@ -56,7 +59,7 @@ export class Upload {
xhr.onload = () => {
this.controllers.delete(id); // 清理控制器
// 构造类似 Response 的对象,支持 headers.get()
const headersMap = new Map<string, string>();
const headersStr = xhr.getAllResponseHeaders();
@ -66,7 +69,7 @@ export class Upload {
headersMap.set(parts[0].toLowerCase(), parts[1]);
}
});
const headers = {
get: (name: string) => headersMap.get(name.toLowerCase()) || null,
has: (name: string) => headersMap.has(name.toLowerCase()),
@ -74,7 +77,7 @@ export class Upload {
headersMap.forEach(callback);
}
};
if (xhr.status >= 200 && xhr.status < 300) {
if (xhr.status === 204) {
resolve({ status: 204, headers });
@ -131,7 +134,7 @@ export class Upload {
// 无进度监听模式(直接 fetch
try {
let result: Response;
if (isPut) {
// S3 预签名上传
const headers: Record<string, string> = {};
@ -167,7 +170,7 @@ export class Upload {
} catch (error) {
this.controllers.delete(id); // 失败后清理控制器
// 人为中断返回204 使general-business处理成功
if (error instanceof DOMException && error.name === 'AbortError') {
throw new DOMException('Upload aborted', 'AbortError');