uni-app 多文件上传:直接循环调用 uni.uploadFile 实现(并行 / 串行双模式)

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 个的场景)。

实现思路:

  1. Array.map 循环文件数组,每个文件生成一个 uni.uploadFile 的 Promise
  2. Promise.allSettled 等待所有请求完成(区别于 Promise.all:即使某个文件失败,其他文件仍会继续上传)
  3. 统一收集 "成功结果" 和 "失败原因",方便后续处理

完整代码:

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 || '图片上传失败');
        }
},
相关推荐
记得坚持2 小时前
vue2插槽
前端·vue.js
带只拖鞋去流浪2 小时前
Vue.js响应式API
前端·javascript·vue.js
Coder_R2 小时前
如何 把 Mac 上的 APK(app) 安装到安卓手机上?
前端·面试
前端小灰狼2 小时前
Ant Design Vue Vue3 table 表头筛选重置不清空Bug
前端·javascript·vue.js·bug
前端付豪2 小时前
11、JavaScript 语法:到底要不要写分号?一文吃透 ASI 与坑点清单
前端·javascript
Copper peas2 小时前
Vue 中的 v-model 指令详解
前端·javascript·vue.js
前端小书生2 小时前
NestJs
前端·nestjs
万少2 小时前
十行代码 带你极速接入鸿蒙6新特性 - 应用内打分评价
前端·harmonyos
一写代码就开心2 小时前
VUE 里面 Object.prototype 是什么,如何使用他
前端