es7提出的async/await概念已经存在有相当长一段时间,具体概念用法就不在这里赘述了,优势在于处理解决then链多层嵌套回调的问题,使得代码更为简单清晰。
本文在这里要讲的是批量上传多张图片时,如果不分批上传可能触发浏览器的并发限制,亦或是图片过多过大导致上传超时,都会影响图片的上传成功率。所以,我们需要分批上传图片时,async/await概念就能很好的解决我们的问题。否则,就只能使用递归来处理,或是只允许单张上传影响用户体验。
其实全部代码和react没有太大关系,只是用到部分特性,是可以适用于任何框架的。至于批量获取图片的组件我直接用的是react-dropzone,可以拖拽图片,当然了,使用原生的
<input type="file" accept={//图片类型} multiple/>也是完全OK的。
预览代码
//处理获取的图片
handleDropFiles = (acceptedFiles) => {
const {
maxCount, //最多上传图片张数
limit
} = this.props;
let { selectedFilesTotalSize, selectedFiles } = this.state;
const _selectedFiles_ = selectedFiles.map(item => item.file); //已经成功获取过的图片
const successFiles = [], //获取成功的图片
rejectedFiles = [], //获取失败的图片
existFiles = []; //已经存在的图片
if (acceptedFiles && acceptedFiles.length) {
for (const file of acceptedFiles) {
if (limit * 1024 < file.size) {
rejectedFiles.push(file);
} else {
const index = _selectedFiles_.findIndex(acceptedFile => this.isSameFile(file, acceptedFile)); //通过文件名文件大小判断是否是同一文件
if (index >= 0) {
existFiles.push(file);
} else {
successFiles.push(file);
}
}
}
}
// 若有不符合条件的图片输出错误信息
let toastMessage = ‘‘;
if (existFiles.length) {
const existFilesName = existFiles.map(item => `"${item.name}"`);
toastMessage = `${existFilesName.join(‘, ‘)}等文件已存在;</br>`;
}
if (rejectedFiles.length) {
const rejectedFilesName = rejectedFiles.map(item => `"${item.name}"`);
toastMessage = `${toastMessage}${rejectedFilesName.join(‘, ‘)}等文件不符合上传条件;</br>`;
}
const incrementLength = successFiles.length;
const selectedFilesLength = selectedFiles.length;
if (incrementLength + selectedFilesLength > maxCount) {
const overflowFiles = successFiles.splice(maxCount - selectedFilesLength);
const overflowFilesName = overflowFiles.map(item => `"${item.name}"`);
toastMessage = `${toastMessage}${overflowFilesName.join(‘, ‘)}等文件超出上传数量的限制;</br>`;
}
toastMessage && this.props.onError( toastMessage );
// 多图预览 如果只需要用云服务上传后的url预览可以将此步骤替换为handleUploadFiles的代码
if (incrementLength) {
// 这里选择了createObjectURL而不是readAsDataURL具体区别不详说了 如果要用readAsDataURL还得Promise.all一下
for (const file of successFiles) {
const dataUrl = URL.createObjectURL(file);
selectedFiles.push({
file,
name: file.name,
size: file.size,
dataUrl,
uploadStatus: ‘beforeUpload‘ //标识图片状态,之后有可能上传失败需要重新上传
});
}
selectedFiles = selectedFiles.map((item, index) => return {...item, {index: index}});
selectedFilesTotalSize = selectedFiles.reduce((previousSize, nextFile) => previousSize + nextFile.size, 0);
this.setState({
selectedFiles,
selectedFilesTotalSize
});
}
}
批量上传代码
// 批量上传获取的图片
handleUploadFiles = async () => {
const {
batchCount, //一组最多上传图片张数(考虑到浏览器并发)
batchLimit //最多上传一组图片大小(考虑到浏览器上传速度限制)
} = this.props;
const { selectedFiles, uploadedFiles } = this.state;
const chunkFiles = chunkFile(selectedFiles.map(file => [‘beforeUpload‘, ‘failed‘].includes(file.uploadStatus))); //根据batchCount&batchLimit给未上传或上传失败的图片组分块
const rate = chunkFiles.length;
for (const [index, chunkFile] of chunkFiles.entries()) {
toast.show(`图片上传中${~~(index+1)/rate*100}%`, ‘loading‘, this.timeout); //这里做了个假的图片已上传率
const uploadFilePromise = chunkFile.map(this.uploadFile);
await Promise.all(uploadFilePromise).then((uploadFiles) => {
for (const file of uploadFiles) {
if (‘error‘ in item) {
selectedFiles.find(item => item.index === file.index).uploadStatus = ‘failed‘; //若上传失败更改selectedFiles里的图片状态
} else {
uploadedFiles.push({ url: file.url });
}
}
});
this.setState({ selectedFiles, uploadedFiles });
this.props.onSuccess(uploadedFiles);
}
toast.hide();
// 分组上传
function chunkFile(files) {
let array = [],
subArray = [],
size = 0;
files.forEach((item, index) => {
size += item.size;
if (size > batchLimit*1024 || subArray.length === batchCount) {
array.push(subArray);
subArray = [item];
size = item.size;
} else {
subArray.push(item);
}
if (index === files.length-1) {
array.push(subArray);
}
});
return array;
}
}
上传图片
// ajax上传单张图片,就是简单的FormData随便看下就好
uploadFile = (file, index) => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append(‘file‘, file);
$.ajax(..., (data)=>{
data = (data && data.data) || {};
resolve({ url: data.url, index });
}, (err) => {
//this.props.onError(err && err.errMsg || ‘上传失败‘); //可以在这根据需求提示第几张图片上传失败
resolve({ error: err, index })
}, this);
});
}