「从零开始的大文件上传 」都 2022 年了我怎么还在写这个话题...(二)

简介: 「从零开始的大文件上传 」都 2022 年了我怎么还在写这个话题...(二)

并发控制


这里之前的 unUploadFileListxhrMap 就派上了用场:


网络异常,图片无法展示
|


是否可以发起请求


还有待上传的文件并且 xhrMap 中正在上传的请求数小于最大并发数


unUploadFileList.length > 0 && xhrMap.size < concurrency


是否结束上传


没有待上传的文件并且 xhrMap 中没有正在进行的请求


isUploaded() {
  return this.unUploadFileList.length === 0 && this.xhrMap.size === 0;
}


完整代码


upload() {
  const { concurrency, xhrMap, unUploadFileList, xhrOptions, fileState } = this;
  return new Promise(resolve => {
    const run = async () => {
      while (unUploadFileList.length > 0 && xhrMap.size < concurrency) {
        const file = unUploadFileList.shift();
        const { xhr, request } = this.requestFactory(file, xhrOptions, this.progressFactory(file));
        xhrMap.set(file, xhr);
        request.finally(()=> {
          xhrMap.delete(file);
          if (this.isUploaded()) {
            resolve();
            this.progressEvent('END', {
              fileState
            });
          } else {
            run();
          }
        });
      }
    };
    run();
  });
}


取消上传


取消上传分为取消单个文件的上传和取消全部未完全上传的文件,这里用到的就是 abort 这个 api,他的语法为:


xhrInstance.abort();


如果该请求已被发出,XMLHttpRequest.abort() 方法将终止该请求。当一个请求被终止,它的  readyState 将被置为 XMLHttpRequest.UNSENT(0),并且请求的 status 置为 0


取消上传的逻辑就是如果该请求在 xhrMap 里就 abort 掉,如果在 unUploadFileList 里就直接把它从数组里移除。


abort(file) {
  if (this.xhrMap.has(file)) {
    this.xhrMap.get(file)?.abort();
  } else {
    const fileIndex = this.unUploadFileList.indexOf(file);
    this.unUploadFileList.splice(fileIndex, 1);
  }
}
abortAll() {
  this.xhrMap.forEach(xhr => xhr?.abort());
  this.unUploadFileList = [];
}


重新上传


当文件上传失败了,我们可以重新上传,这个就很简单了,就是把 file 添加到 unUploadFileList 中,当然,重新上传时有可能已经 upload 结束,此时就重新触发 upload


redoFile(file) {
    this.fileState.failCount--;
    this.unUploadFileList.push(file);
    if (this.isUploaded()) {
      this.upload();
    }
}


完整代码


class ZetaUploader {
  fileState = {
    failCount: 0,
    successCount: 0,
    abortCount: 0
  }
  /* progressEvent arguments
  * @params {Object} [params]
  * @params {ENUM} [params?.state] FAIL PROGRESS SUCCESS END
  * @params {Info} [params?.info]
  * @params {File} [info?.file]
  * @params {Number} [info?.progress]
  * @params {FileState} [info?.fileState]
  */
  progressEvent = () => { };
  concurrency = 1;
  xhrMap = null;
  unUploadFileList = [];
  /*
  * @params {Object} [xhrOptions]
  * @params {String} [params.url]
  * @params {String} [params.method]
  * @params {Object} [params.headers]
  * @params {Function} [params.getXhrDataByFile]
  */
  xhrOptions = null; 
  static isRequestSuccess(progressEvent) {
    return String(progressEvent.target.status).startsWith('2');
  }
  constructor(progressEvent, fileList, concurrency, xhrOptions) {
    const basicXhrOptions = {
      url: '',
      method: 'post',
      headers: [],
      getXhrDataByFile: file => {
        const formData = new FormData();
        formData.append('file', file);
        return formData;
      }
    };
    // BASIC ATTRS
    this.progressEvent = progressEvent;
    this.concurrency = concurrency;
    this.xhrOptions = Object.assign(basicXhrOptions, xhrOptions);
    // COMPUTED ATTRS
    this.unUploadFileList = fileList;
    this.xhrMap = new Map();
  }
}
class BasicZetaUploader extends ZetaUploader {
  constructor(progressEvent, fileList, concurrency, xhrOptions) {
    super(progressEvent, fileList, concurrency, xhrOptions);
  }
  progressFactory(file) {
    return e => {
      this.progressEvent('PROGRESS', {
        file,
        progress: parseInt(String((e.loaded / e.total) * 100))
      });
    };
  }
  /*
  * @params {File} [file]
  * @params {xhrOptions} [xhrOptions]
  * @params {Function} [onProgress]
  */
  requestFactory(file, xhrOptions, onProgress) {
    const { url, method, headers, getXhrDataByFile } = xhrOptions;
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);
    Object.keys(headers).forEach(key => xhr.setRequestHeader(key, headers[key]));
    let _resolve = () => { };
    let _reject = () => { };
    xhr.onprogress = onProgress;
    xhr.onload = e => {
      // 需要加入response的判断
      if (ZetaUploader.isRequestSuccess(e)) {
        this.fileState.successCount++;
        this.progressEvent('SUCCESS', {
          file
        });
        _resolve({
          data: e.target.response
        });
      } else {
        this.fileState.failCount++;
        this.progressEvent('FAIL', {
          file
        });
        _reject();
      }
    };
    xhr.onerror = () => {
      this.fileState.failCount++;
      this.progressEvent('FAIL', {
        file
      });
      _reject();
    };
    xhr.onabort = () => {
      this.fileState.abortCount++;
      _resolve({
        data: null
      });
    };
    const request = new Promise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
      xhr.send(getXhrDataByFile(file));
    });
    return {
      xhr,
      request
    };
  }
  upload() {
    const { concurrency, xhrMap, unUploadFileList, xhrOptions, fileState } = this;
    return new Promise(resolve => {
      const run = async () => {
        while (unUploadFileList.length > 0 && xhrMap.size < concurrency) {
          const file = unUploadFileList.shift();
          const { xhr, request } = this.requestFactory(file, xhrOptions, this.progressFactory(file));
          xhrMap.set(file, xhr);
          request.finally(()=> {
            xhrMap.delete(file);
            if (this.isUploaded()) {
              resolve();
              this.progressEvent('END', {
                fileState
              });
            } else {
              run();
            }
          });
        }
      };
      run();
    });
  }
  isUploaded() {
    return this.unUploadFileList.length === 0 && this.xhrMap.size === 0;
  }
  abort(file) {
    if (this.xhrMap.has(file)) {
      this.xhrMap.get(file)?.abort();
    } else {
      const fileIndex = this.unUploadFileList.indexOf(file);
      this.unUploadFileList.splice(fileIndex, 1);
    }
  }
  abortAll() {
    this.xhrMap.forEach(xhr => xhr?.abort());
    this.unUploadFileList = [];
  }
  redoFile(file) {
    this.fileState.failCount--;
    this.unUploadFileList.push(file);
    if (this.isUploaded()) {
      this.upload();
    }
  }
}
export {
  BasicZetaUploader
};


结束语


网络异常,图片无法展示
|


本篇文章到这里就结束了,「本系列的」下一篇文章不出意外的话会带着大家了解文件分片与断点续传的原理和实现思路。


最后,下一篇文章见~


✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

我生于长空,长于烈日;
我翱翔于风,从未远去。

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

相关文章
|
前端开发 微服务
Element-Plus 表格 el-table 如何支持分页多选
Element-Plus 表格 el-table 如何支持分页多选
|
算法 Java 关系型数据库
Springboot yml配置参数加密 ,jasypt自定义解密器(拓展篇)
Springboot yml配置参数加密 ,jasypt自定义解密器(拓展篇)
1379 0
Springboot yml配置参数加密 ,jasypt自定义解密器(拓展篇)
|
消息中间件 监控 Java
利用Java构建高效的消息推送系统
利用Java构建高效的消息推送系统
|
7月前
|
Java 应用服务中间件 Maven
在IntelliJ IDEA中如何配置使用Maven以创建Tomcat环境
所以,别担心这些工具看起来有些吓人,实际上这些都是为了帮助你更好的完成工作的工具,就像超市里的各种烹饪工具一样,尽管它们看起来可能很复杂,但只要你学会用,它们会为你烹饪出一道道美妙的食物。这就是学习新技能的乐趣,让我们一起享受这个过程,攀登知识的高峰!
485 27
|
9月前
|
数据可视化 数据挖掘 BI
|
8月前
|
数据采集 Web App开发 文字识别
Python爬虫多次请求后被要求验证码的应对策略
Python爬虫多次请求后被要求验证码的应对策略
|
人工智能 边缘计算 物联网
云计算的未来:五大趋势与技术变革
【6月更文挑战第25天】云计算未来五大趋势: 1. 边缘计算与物联网结合,减少延迟,增强实时性。 2. AI与云计算融合,提升智能服务效率。 3. 量子计算的潜力,革新云计算处理能力。 4. 混合云和多云策略成主流,提供灵活安全选项。 5. 可持续性发展,绿色云计算降低环境影响。
1893 6
|
Unix 网络虚拟化 C++
VS2022+Qt5.14.2成功编译MITK2022.10
使用VS2022和Qt5.14.2成功编译MITK2022.10的过程,包括编译结果的截图、遇到的编译问题的解决方法、两个重要的注意事项(patch文件格式的修改和ITK-gitclone-lastrun文件的存在),以及参考链接。文中详细描述了如何解决编译过程中遇到的错误C2220和警告C4819,以及如何修改文件编码和尾行格式。
967 1
VS2022+Qt5.14.2成功编译MITK2022.10
|
JavaScript 前端开发 C++
使用 Vue-Cli 创建 Vue3+TS 项目并整合 ElementPlus、Axios 等组件或插件
本文详细记录了如何使用Vue-Cli工具创建一个Vue3+TypeScript项目,并整合ElementPlus组件库和Axios等插件,包括项目的创建、配置、启动和插件封装使用等步骤。
853 0
|
SQL 关系型数据库 MySQL
阿里云数据库使用方法,从购买、创建数据库账号密码到连接数据库全流程
阿里云数据库使用方法,从购买、创建数据库账号密码到连接数据库全流程,阿里云数据库怎么使用?阿里云百科整理阿里云数据库从购买到使用全流程,阿里云支持MySQL、SQL Server、PostgreSQL和MariaDB等数据库引擎,阿里云数据库具有高可用、高容灾特性,阿里云提供数据库备份、恢复、迁移全套解决方案
1356 0