uni-app 多文件上传:直接循环调用 uni.uploadFile 实现(并行 / 串行双模式)
在 uni-app 开发中,多文件上传是高频需求 ------ 比如表单附件上传、多图上传等。很多同学会纠结 "怎么优雅地循环调用 uni.uploadFile
",既要保证上传稳定性,又要适配不同场景(比如是否需要按顺序传)。
今天就分享一套直接基于 uni.uploadFile
循环实现的多文件上传方案,包含「并行上传」和「串行上传」两种模式,附带完整代码和开发避坑指南。
1.1 uni.uploadFile
核心参数
uni.uploadFile
是 uni-app 封装的原生上传 API,多文件上传时需要注意:
files
:接收文件数组 (即使单文件,也需要用数组包裹,比如[file]
,但是主包发现采用这个参数,导致无法预览上传的图片)filePath
: 接收单文件地址和files冲突,这里采用这个参数name
: 文件名称和filePath参数绑定一起,主包这里直接采用"file"url
:后端上传接口地址(提前存在this.uploadAction
中)header
:请求头(通常需要携带 Token 做权限验证,提前通过getHeader()
获取)success/fail
:上传结果回调(需要处理 JSON 解析异常)
1.2 前置处理:请求头统一获取
因为所有文件上传的请求头是一致的(比如 Token 不变),所以不需要在循环里重复调用 getHeader()
------ 在多文件上传方法开头调用一次即可,减少性能开销:
javascript
运行
javascript
// 假设在页面/组件的 data 中定义
data() {
return {
uploadAction: "https://your-server.com/api/upload", // 后端上传接口
Header: {} // 存储请求头(如 Token)
};
},
methods: {
// 获取请求头(比如从缓存取 Token)
getHeader() {
const token = uni.getStorageSync("token");
this.Header = {
"Content-Type": "multipart/form-data",
"Authorization": `Bearer ${token}`
};
}
}
二、核心实现:两种上传模式任选
接下来是重点 ------ 直接循环调用 uni.uploadFile
,实现两种常用的上传模式。每种模式都包含完整的错误处理,确保结果可追溯。
主包查看了uniapp的api文档在uni.uploadFile中仅APP端支持多文件上传,在微信小程序H5中还需要采用循环调用,因此这里主包直接选择写循环。
2.1 模式 1:并行上传(默认推荐)
特点:同时发起所有文件的上传请求,速度快;但对服务器并发压力较大(适合小文件、数量≤10 个的场景)。
实现思路:
- 用
Array.map
循环文件数组,每个文件生成一个uni.uploadFile
的 Promise - 用
Promise.allSettled
等待所有请求完成(区别于Promise.all
:即使某个文件失败,其他文件仍会继续上传) - 统一收集 "成功结果" 和 "失败原因",方便后续处理
完整代码:
javascript
/**
* 多文件上传(支持并行/串行)
* @param {Array} files - 要上传的文件数组(从 uni.chooseImage 等 API 获取)
* @param {Boolean} sequential - 是否串行上传(默认 false = 并行)
* @returns {Promise<Array>} - 所有文件的上传结果(含成功/失败信息)
*/
uploadMultipleFiles(files, sequential = false) {
// 1. 提前获取请求头(只调用一次)
this.getHeader();
// 2. 并行上传逻辑
if (!sequential) {
// 循环生成每个文件的上传 Promise
const uploadPromises = files.map((file, index) => {
return new Promise((resolve) => { // 这里用 resolve 统一收结果,避免中断
uni.uploadFile({
url: this.uploadAction,
filePath: file,
name: 'file',
header: this.Header,
// 上传成功处理
success: (res) => {
try {
// 解析后端返回的 JSON(防止后端返回非 JSON 格式)
const result = JSON.parse(res.data);
// 后端约定:成功时返回 data.url
if (result.code === 200 && result.data?.url) {
resolve({
index, // 保留原文件在数组中的索引(方便对应)
file, // 原始文件信息
url: result.data.url, // 上传后的 URL
success: true,
error: null
});
} else {
// 后端返回失败(如文件格式不允许)
resolve({
index,
file,
url: null,
success: false,
error: new Error(result.msg || "文件上传失败")
});
}
} catch (e) {
// JSON 解析失败(如后端接口报错返回 HTML)
resolve({
index,
file,
url: null,
success: false,
error: new Error("上传结果解析失败:" + e.message)
});
}
},
// 上传失败处理(如网络错误、超时)
fail: (err) => {
resolve({
index,
file,
url: null,
success: false,
error: new Error("上传请求失败:" + err.errMsg)
});
}
});
});
});
// 等待所有请求完成,返回统一结果
return Promise.allSettled(uploadPromises).then((results) => {
// 提取实际结果(allSettled 返回的是 {status, value} 结构)
return results.map((res) => res.value);
});
}
// 下文讲串行上传...
}
2.2 模式 2:串行上传(按顺序上传)
特点:一个文件上传完成后,再传下一个;速度慢,但能保证顺序,且对服务器压力小(适合大文件、有顺序依赖的场景,比如上传后需要按顺序关联)。
实现思路:用「递归」控制上传顺序 ------ 每次上传完当前文件(index),再调用自身上传下一个(index+1),直到所有文件处理完。
在上面的 uploadMultipleFiles
方法中,补充串行逻辑:
在上面的 uploadMultipleFiles
方法中,补充串行逻辑:
javascript
运行
php
// 接上文的 if (!sequential) 之后
else {
const uploadResults = []; // 存储所有结果
// 递归函数:上传第 index 个文件
const uploadNext = (index) => {
// 终止条件:所有文件处理完,返回结果
if (index >= files.length) {
return Promise.resolve(uploadResults);
}
const currentFile = files[index];
return new Promise((resolve) => {
uni.uploadFile({
url: this.uploadAction,
filePath: currentFile,
name: 'file',
header: this.Header,
success: (res) => {
try {
const result = JSON.parse(res.data);
if (result.code === 200 && result.data?.url) {
uploadResults.push({
index,
file: currentFile,
url: result.data.url,
success: true,
error: null
});
} else {
uploadResults.push({
index,
file: currentFile,
url: null,
success: false,
error: new Error(result.msg || "文件上传失败")
});
}
// 上传下一个文件(递归)
resolve(uploadNext(index + 1));
} catch (e) {
uploadResults.push({
index,
file: currentFile,
url: null,
success: false,
error: new Error("解析失败:" + e.message)
});
resolve(uploadNext(index + 1));
}
},
fail: (err) => {
uploadResults.push({
index,
file: currentFile,
url: null,
success: false,
error: new Error("请求失败:" + err.errMsg)
});
resolve(uploadNext(index + 1)); // 即使失败,也继续传下一个(可按需修改)
}
});
});
};
// 从第 0 个文件开始上传
return uploadNext(0);
}
使用逻辑代码
javascript
/**
* 选择图片
*/
async handleSelectImage(e) {
try {
const tempFilePaths = e.tempFilePaths;
if (tempFilePaths && tempFilePaths.length > 0) {
const fileUrls = await this.uploadMultipleFiles(tempFilePaths)
fileUrls.forEach((item)=> {
this.imgList.push({
url: item.url,
name: ''
});
})
}
} catch (error) {
console.error('图片上传失败:', error);
showToast(error.message || '图片上传失败');
}
},