Promise.all
在JavaScript中,Promise是一种处理异步操作的机制,而Promise.all是一个强大的工具,用于并行处理多个Promise。当所有Promise都成功时,Promise.all返回一个新的Promise,其状态为Fulfilled,并携带一个包含所有Promise成功结果的数组;如果任何一个Promise失败,Promise.all立即返回一个Rejected的Promise,并携带第一个失败的Promise的错误信息。
为什么需要Promise.all
在实际开发中,有些场景需要并行执行多个异步操作的情况。例如,同时从多个API获取数据、同时上传多个文件、同时读取本地多个文件。使用Promise.all可以非常方便地实现这一需求,提高代码的可读性和可维护性。
all的使用
Promise.all(iterable);
iterable: 一个可迭代对象(一般是一个数组),包含多个Promise对象。
1. 并行获取天气信息、新闻信息和股票信息
// 模拟异步操作:获取天气信息
function fetchWeather() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ weather: "Sunny", temperature: 30 });
}, 1000);
});
}
// 模拟异步操作:获取新闻信息
function fetchNews() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(["新闻1", "新闻2", "新闻3"]);
}, 2000);
});
}
// 模拟异步操作:获取股票信息
function fetchStocks() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ stock: "AAPL", price: 150 });
}, 1500);
});
}
// 使用Promise.all并行处理这些异步请求
Promise.all([fetchWeather(), fetchNews(), fetchStocks()])
.then((results) => {
const [weather, news, stocks] = results;
console.log("天气信息: ", weather);
console.log("新闻信息: ", news);
console.log("股票信息: ", stocks);
})
.catch((error) => {
console.error("出错了: ", error);
});
2. 并行读取多个文件内容
假设我们有三个文件:file1.txt、file2.txt、file3.txt,我们希望并行读取它们的内容,并在所有内容读取完成后进行处理。
const fs = require('fs').promises;
// 读取文件的异步函数
function readFile(fileName) {
return fs.readFile(fileName, 'utf-8');
}
// 使用Promise.all并行读取多个文件
Promise.all([readFile('file1.txt'), readFile('file2.txt'), readFile('file3.txt')])
.then((contents) => {
contents.forEach((content, index) => {
console.log(`文件${index + 1}内容: ${content}`);
});
})
.catch((error) => {
console.error("读取文件出错: ", error);
});
3、批量并发上传文件
假设存在一个需求,允许用户一次性上传大量的文本文件,但是一次性将文件通过一个请求上传会造成网络负担或者导致请求失败,如果每个文件触发请求上传一次会导致上传时间太长,我们可以通过promise.all 分批次并发上传。
<template>
<div>
<el-upload
ref="upload"
class="upload-demo"
action="#"
:auto-upload="false"
:file-list="fileList"
:on-change="handleFileChange"
multiple
accept="text/plain"
:on-remove="handleRemove"
>
<el-button slot="trigger" size="small" type="primary">选择文件</el-button>
<el-button size="small" type="success" @click="submitUpload">上传到服务器</el-button>
</el-upload>
</div>
</template>
<script>
import { uploadNovelChapterFile } from '@/api/novel-manage'
export default {
props: {
id: {
type: [String, Number],
default: ''
},
novelList: {
type: Array,
default: () => []
}
},
data() {
return {
fileList: [],
results: [],
batchSize: 5, // 定义每批上传的文件数量
uploadedFiles: new Set(),
isUploading: false, // 添加上传状态标志
fileIndexMap: new Map() // 记录文件的原始索引
}
},
methods: {
handleFileChange(file, fileList) {
this.fileIndexMap.clear()
this.fileList = fileList.map((file, index) => {
this.fileIndexMap.set(file.uid, index)
return {
...file,
status: this.uploadedFiles.has(file.uid) ? 'success' : 'ready'
}
})
},
handleRemove(file) {
this.uploadedFiles.delete(file.uid)
this.fileIndexMap.delete(file.uid)
},
submitUpload() {
if (this.fileList.length === 0) {
this.$message.error('请选择txt文件后上传')
return
}
this.isUploading = true // 开始上传时设置状态标志
const totalBatches = Math.ceil(this.fileList.length / this.batchSize)
const uploadPromises = []
for (let i = 0; i < totalBatches; i++) {
uploadPromises.push(this.uploadBatch(i))
}
Promise.all(uploadPromises)
.then(() => {
this.isUploading = false // 所有批次上传完成时重置状态标志
this.sortResults() // 按照原始索引排序结果
this.novelListChange() // 所有批次上传完成后调用 novelListChange
this.$message.success('所有文件上传完成')
})
.catch(() => {
this.isUploading = false // 处理错误时也需要重置状态标志
this.$message.error('文件上传失败')
})
},
uploadBatch(batchIndex) {
const formData = new FormData()
const batchFiles = this.fileList.slice(batchIndex * this.batchSize, (batchIndex + 1) * this.batchSize)
batchFiles.forEach(file => {
formData.append('files', file.raw)
})
return uploadNovelChapterFile({ id: this.id }, formData).then(res => {
this.results.push(...batchFiles.map((file, index) => ({
...res.data[index],
originalIndex: this.fileIndexMap.get(file.uid)
})))
batchFiles.forEach(file => this.uploadedFiles.add(file.uid))
this.updateFileListStatus()
})
},
clearFiles() {
this.$refs.upload.clearFiles()
this.uploadedFiles.clear()
this.fileIndexMap.clear()
},
updateFileListStatus() {
this.fileList = this.fileList.map(file => ({
...file,
status: this.uploadedFiles.has(file.uid) ? 'success' : 'ready'
}))
},
sortResults() {
this.results.sort((a, b) => a.originalIndex - b.originalIndex)
},
novelListChange() {
if (this.isUploading) return // 上传过程中不调用 novelListChange
if (!this.results.length > 0) return
const fileList = this.results.map(item => ({
url: item.chapterUrl,
fileName: item.fileName
}))
this.$emit('update:novel-list', fileList)
}
}
}
</script>
all的返回值
Promise.all 返回一个新的Promise对象,该Promise对象的状态取决于iterable参数中的所有Promise对象的状态。
- 所有Promise都成功完成时
- 如果iterable中的所有Promise都成功完成,Promise.all 返回的Promise对象的状态会变为fulfilled(已成功)。
- 返回的Promise对象会携带一个数组,数组中的每一项对应于iterable中每个Promise对象成功完成时的结果。数组的顺序与iterable中Promise对象的顺序一致。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, "foo", 42]
});
- 任意一个Promise失败时
- 如果iterable中的任何一个Promise失败,Promise.all 返回的Promise对象的状态会变为rejected(已失败)。
- 返回的Promise对象会携带一个错误信息,这个错误信息对应于iterable中第一个被拒绝的Promise对象的错误原因。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
setTimeout(reject, 100, 'error');
});
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error); // "error"
});
一旦Promise.all中的任何一个Promise失败(即状态变为rejected),整个Promise.all返回的Promise将会立即变为rejected,并且不会再等待其他Promise的完成。返回的Promise只会携带第一个被拒绝的Promise的错误信息。