「从零开始的大文件上传 」都 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
};


结束语


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


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


最后,下一篇文章见~


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

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

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

相关文章
|
前端开发
前端学习笔记202305学习笔记第二十四天-文件读取2
前端学习笔记202305学习笔记第二十四天-文件读取2
74 0
|
7月前
奇淫技巧系列第三篇:阅读源码时基于一组快捷键让我们知道身在何方!
奇淫技巧系列第三篇:阅读源码时基于一组快捷键让我们知道身在何方!
|
前端开发
前端学习笔记202305学习笔记第二十四天-文件读取1
前端学习笔记202305学习笔记第二十四天-文件读取1
53 0
|
前端开发
前端学习笔记202305学习笔记第二十四天-文件写入
前端学习笔记202305学习笔记第二十四天-文件写入
74 0
|
uml 开发者 Windows
推荐5款冷门小工具,看一看有没有你喜欢的?
每个人的电脑中都会安装很多软件,可能还保留着很多不为人知的冷门软件。不过虽然冷门,但绝不意味着低能,相反很多冷门软件的功能十分出色。闲话少说,接下来我就给大家推荐5款冷门小工具,看一看有没有你喜欢的。
195 0
推荐5款冷门小工具,看一看有没有你喜欢的?
|
SQL 索引
yyds,SQL基础教程,进阶必须(建议收藏!) 下
yyds,SQL基础教程,进阶必须(建议收藏!) 下
118 0
|
SQL 存储 Oracle
yyds,SQL基础教程,进阶必须(建议收藏!) 上
yyds,SQL基础教程,进阶必须(建议收藏!) 上
158 0
yyds,SQL基础教程,进阶必须(建议收藏!)   上
|
SQL
yyds,SQL基础教程,进阶必须(建议收藏!) 中
yyds,SQL基础教程,进阶必须(建议收藏!) 中
126 0
yyds,SQL基础教程,进阶必须(建议收藏!) 中
|
存储 缓存 算法
写给前端的算法进阶指南,我是如何两个月零基础刷200题
最近国内大厂面试中,出现 LeetCode 真题考察的频率越来越高了。我也观察到有越来越多的前端同学开始关注算法这个话题。
|
运维 监控 Java
【高效编码】JDK自带的命令行工具的使用还用不清楚的地方?快来看看这篇文章吧!!!
您好,我是码农飞哥,感谢您阅读本文!如果此文对您有所帮助,请毫不犹豫的一键三连吧。小伙伴们有啥想看的,想问的,欢迎积极留言告诉我喔。
155 0
【高效编码】JDK自带的命令行工具的使用还用不清楚的地方?快来看看这篇文章吧!!!