在前端实现多个文件逐个上传,可以通过以下几种方式实现:
1. 使用 async/await 顺序上传
async function uploadFilesSequentially(files) {
for (let i = 0; i < files.length; i++) {
try {
const formData = new FormData();
formData.append('file', files[i]);
formData.append('filename', files[i].name);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log(`文件 ${files[i].name} 上传成功`, result);
// 可以在这里更新上传进度
updateProgress(i + 1, files.length);
} catch (error) {
console.error(`文件 ${files[i].name} 上传失败:`, error);
// 可以选择继续上传剩余文件,或停止上传
// break; // 如果上传失败就停止
}
}
console.log('所有文件上传完成');
}
2. 使用递归实现顺序上传
function uploadFilesRecursively(files, index = 0) {
if (index >= files.length) {
console.log('所有文件上传完成');
return Promise.resolve();
}
const file = files[index];
const formData = new FormData();
formData.append('file', file);
return fetch('/api/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(result => {
console.log(`文件 ${file.name} 上传成功`);
updateProgress(index + 1, files.length);
// 继续上传下一个文件
return uploadFilesRecursively(files, index + 1);
})
.catch(error => {
console.error(`文件 ${file.name} 上传失败:`, error);
// 可以选择继续上传或停止
return uploadFilesRecursively(files, index + 1);
});
}
3. 完整的组件示例
<template>
<div>
<input
type="file"
multiple
@change="handleFileChange"
/>
<button @click="uploadFiles">上传文件</button>
<!-- 进度显示 -->
<div v-if="uploading">
<div>上传进度: {{ uploadedCount }} / {{ totalFiles }}</div>
<div>当前文件: {{ currentFileName }}</div>
<progress
:value="uploadedCount"
:max="totalFiles"
></progress>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const files = ref([]);
const uploading = ref(false);
const uploadedCount = ref(0);
const totalFiles = ref(0);
const currentFileName = ref('');
// 处理文件选择
const handleFileChange = (event) => {
files.value = Array.from(event.target.files);
};
// 顺序上传文件
const uploadFiles = async () => {
if (!files.value.length) return;
uploading.value = true;
uploadedCount.value = 0;
totalFiles.value = files.value.length;
for (let i = 0; i < files.value.length; i++) {
const file = files.value[i];
currentFileName.value = file.name;
try {
await uploadSingleFile(file);
uploadedCount.value++;
} catch (error) {
console.error(`文件 ${file.name} 上传失败:`, error);
// 可以在这里处理错误,比如记录失败的文件
}
}
uploading.value = false;
console.log('所有文件上传完成');
};
// 上传单个文件
const uploadSingleFile = async (file) => {
const formData = new FormData();
formData.append('file', file);
formData.append('timestamp', Date.now());
// 可以添加其他参数
formData.append('userId', '123');
formData.append('category', 'documents');
const response = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
// 显示上传进度(可选)
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`文件 ${file.name} 上传进度: ${percent}%`);
}
});
return response.data;
};
</script>
4. 使用 Promise.reduce 实现顺序执行
import axios from 'axios';
async function sequentialUpload(files) {
const results = await files.reduce(async (previousPromise, file, index) => {
const previousResults = await previousPromise;
console.log(`开始上传第 ${index + 1} 个文件: ${file.name}`);
try {
const formData = new FormData();
formData.append('file', file);
const response = await axios.post('/api/upload', formData);
previousResults.push({
file: file.name,
success: true,
data: response.data
});
} catch (error) {
previousResults.push({
file: file.name,
success: false,
error: error.message
});
}
return previousResults;
}, Promise.resolve([]));
return results;
}
5. 并发控制(推荐)
如果需要兼顾效率和顺序,可以使用并发控制:
class FileUploader {
constructor(maxConcurrent = 1) {
this.maxConcurrent = maxConcurrent; // 最大并发数
this.queue = [];
this.activeCount = 0;
this.results = [];
}
addFile(file) {
this.queue.push(file);
}
async uploadAll() {
const batches = [];
// 将文件分批
for (let i = 0; i < this.queue.length; i += this.maxConcurrent) {
batches.push(this.queue.slice(i, i + this.maxConcurrent));
}
// 按批次顺序执行
for (const batch of batches) {
const batchPromises = batch.map(file => this.uploadSingle(file));
const batchResults = await Promise.allSettled(batchPromises);
this.results.push(...batchResults);
}
return this.results;
}
async uploadSingle(file) {
const formData = new FormData();
formData.append('file', file);
return axios.post('/api/upload', formData);
}
}
// 使用示例
const uploader = new FileUploader(3); // 最多3个并发
files.forEach(file => uploader.addFile(file));
uploader.uploadAll().then(results => {
console.log('上传结果:', results);
});
关键注意事项:
-
错误处理:每个文件上传都应该有独立的错误处理
-
进度反馈:给用户显示上传进度
-
取消上传:提供取消上传的功能
-
重试机制:对失败的上传提供重试功能
-
性能优化:大文件可以考虑分片上传
-
内存管理:及时释放不再需要的文件对象
选择哪种方案取决于你的具体需求:
-
如果需要严格的顺序执行,使用方案1或2
-
如果需要更好的性能,可以使用方案5的并发控制
-
如果需要在Vue/React中使用,可以结合框架的特性