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

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

并发控制


这里之前的 unUploadFileList 和 xhrMap 就派上了用场:


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


是否可以发起请求:


还有待上传的文件并且 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
};


结束语


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


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


最后,下一篇文章见~


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

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

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

相关文章
|
存储 分布式计算 监控
Hologres产品介绍与技术揭秘
近年来,随着数据实时化的诉求加剧,催生了一系列的实时数仓架构,Lambda架构也应运而生,但是随着场景的复杂度和业务多维需求,Lambda架构的痛点也越来越明显。HSAP的理念则是服务分析一体化,在本文中,来自阿里巴巴的资深技术专家将会深度剖析HSAP技术实现Hologres的设计原理,解读其产品典型场景。
13706 0
Hologres产品介绍与技术揭秘
|
9月前
|
JSON JavaScript API
Python数据解析实战:用json模块轻松处理API数据
Python数据解析实战:用json模块轻松处理API数据
413 134
|
10月前
|
数据采集 弹性计算 供应链
阿里云服务器ECS抢占式实例是什么?抢占式实例优缺点分析
阿里云ECS抢占式实例(原竞价实例)是一种低成本、按小时结算的付费模式,价格最低可至按量付费的1折,最高节省90%成本。适用于无状态、容错性强的业务,如大数据分析、科学计算、测试等。但存在被中断回收风险,当市场价格波动或资源紧张时实例将被释放,数据可能丢失,故不建议用于数据库等有状态应用。支持设置自动出价或手动限价,可选1小时保护期以提升稳定性。详情参考阿里云官方文档。
|
SQL Java 数据库连接
谈谈mybatispllus的分页原理
【5月更文挑战第23天】MyBatis 是一个流行的持久层框架,它支持自定义 SQL、存储过程以及高级映射。分页是开发中常见的需求,尤其是在处理大量数据时,合理的分页可以有效提升系统性能和用户体验。MyBatis 提供了几种不同的分页方式,本文将对这些方式进行介绍。
462 2
|
9月前
|
机器学习/深度学习 存储 人工智能
破解 AI 智能体 “浮光行为”:从底层逻辑到企业级落地的系统性优化方案
随着AI智能体迈向企业级落地,浮光行为成为核心挑战:表面逻辑通顺却偏离任务目标。其根源在于Transformer架构在长上下文中的注意力稀释、KV缓存噪声累积与推理链断裂,导致任务死循环、误调用高风险工具等问题。通过动态记忆管理、双Agent校验架构与结构化思维链等系统性方案,可有效抑制该现象。掌握这些技术是AI Agent搭建师实现从Demo到生产跨越的关键,也是职业进阶的核心竞争力。未来将向具备自我纠偏能力的元认知智能体演进。
432 3
|
10月前
|
SQL 容灾 Nacos
Seata的部署和集成
本文介绍Seata TC服务器的部署与微服务集成,包括下载、解压、配置修改、数据库表创建及启动步骤,并实现基于Nacos的高可用与异地容灾集群,提升分布式事务可靠性。
|
9月前
|
人工智能 NoSQL Serverless
[最佳实践] Serverless架构下的Agent编排:智能体来了(西南总部)AI Agent指挥官的冷启动优化与状态持久化指南
本文将复盘 智能体来了(西南总部) 技术团队的一套生产级架构。他们通过将 AI Agent 指挥官 与 AI 调度官 拆解为细粒度的函数,利用 NAS 挂载、镜像加速 解决冷启动,利用 Tablestore 实现状态外置,构建了一套“睡后收入”级的低成本 Agent 架构。
|
12月前
|
人工智能 Java 物联网
Java与边缘AI:构建离线智能的物联网与移动应用
随着边缘计算和终端设备算力的飞速发展,AI推理正从云端向边缘端迁移。本文深入探讨如何在资源受限的边缘设备上使用Java构建离线智能应用,涵盖从模型优化、推理加速到资源管理的全流程。我们将完整展示在Android设备、嵌入式系统和IoT网关中部署轻量级AI模型的技术方案,为构建真正实时、隐私安全的边缘智能应用提供完整实践指南。
737 3
|
存储 芯片
51单片机--LCD1602
51单片机--LCD1602
855 1
|
人工智能 安全 API
AI 解决方案的安全控制设计与实施
AI 解决方案的安全控制设计与实施涵盖数据安全、模型安全、系统安全及合规治理四大领域。通过数据加密、访问控制、差分隐私等手段保障数据安全;采用对抗训练、联邦学习确保模型安全;利用容器化部署、可信执行环境维护系统安全;并遵循 GDPR 等法规,进行红队测试和应急响应,确保 AI 全生命周期的安全性与合规性。

热门文章

最新文章