react利用await/async實現批量上傳圖片可預覽可限制上傳圖片數量

es7提出的async/await概念已經存在有至關長一段時間,具體概念用法就不在這裏贅述了,優點在於處理解決then鏈多層嵌套回調的問題,使得代碼更爲簡單清晰。react

本文在這裏要講的是批量上傳多張圖片時,若是不分批上傳可能觸發瀏覽器的併發限制,亦或是圖片過多過大致使上傳超時,都會影響圖片的上傳成功率。因此,咱們須要分批上傳圖片時,async/await概念就能很好的解決咱們的問題。不然,就只能使用遞歸來處理,或是隻容許單張上傳影響用戶體驗。ajax

其實所有代碼和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 
        });
    }
}

批量上傳代碼app

// 批量上傳獲取的圖片
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);
    });
}
相關文章
相關標籤/搜索