初体验web worker

前言

本文将使用 Web WorkersMinIO 实现文件上传的Demo。如果您发现任何错误或有更优的解决方案,欢迎在评论区提出宝贵意见。

资源

aws-sdk
DOMParser

目录结构

  • utils
    • aws-sdk.min.js
    • domparser_bundle.js
  • workers
    • upload.js
  • index.html

1. 创建目录和文件

在根目录下创建utils和worker目录,index.html。
将资源文件放在utils目录下。
worker目录下创建upload.js。
index.html如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>初体验 web worker</title>
  </head>
  <body>
    <!-- 文档地址 https://developer.mozilla.org/zh-CN/docs/Web/API/File/webkitRelativePath -->
    <input id="upload-directory" type="file" webkitdirectory multiple />
    <button id="cancel">取消</button>
    <script>
      const S3Worker = new Worker("/workers/upload.js");
      const uploadDirectoryInput = document.getElementById("upload-directory");
      uploadDirectoryInput?.addEventListener("change", (e) => {
        S3Worker.postMessage({ action: "uploads", payload: e.target.files });
      });
      const cancel = document.getElementById("cancel");
      cancel?.addEventListener("click", () => {
        S3Worker.postMessage({ action: "abort" });
      });
    </script>
  </body>
</html>

upload.js如下:


// worker内不能使用window对象
var window = self;

// domparser
importScripts("/utils/domparser_bundle.js");
// worker内没有DOMParser
var DOMParser = xmldom.DOMParser;

// aws-sdk
importScripts("/utils/aws-sdk.min.js");

// 文件状态
const FileStatus = {
  waiting: "waiting", //等待上传
  success: "success", //上传成功
  failed: "failed", // 上传失败
  cancel: "cancel", // 取消
  uploading: "uploading", // 上传中
};

/**
 * @description uuid
 * @returns {string}
 */
const getCryptoUuid = () => {
  if (typeof crypto === "object") {
    if (typeof crypto.randomUUID === "function") {
      return crypto.randomUUID();
    }
    if (
      typeof crypto.getRandomValues === "function" &&
      typeof Uint8Array === "function"
    ) {
      const callback = (c) => {
        const num = Number(c);
        return (
          num ^
          (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))
        ).toString(16);
      };
      return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, callback);
    }
  }
  let timestamp = new Date().getTime();
  let perforNow =
    (typeof performance !== "undefined" &&
      performance.now &&
      performance.now() * 1000) ||
    0;
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
    let random = Math.random() * 16;
    if (timestamp > 0) {
      random = (timestamp + random) % 16 | 0;
      timestamp = Math.floor(timestamp / 16);
    } else {
      random = (perforNow + random) % 16 | 0;
      perforNow = Math.floor(perforNow / 16);
    }
    return (c === "x" ? random : (random & 0x3) | 0x8).toString(16);
  });
};

/**
 * @description 创建S3实例
 * @returns {AWS.S3}
 */
const createS3 = () => {
  const accessKeyId = ""; // 账号
  const secretAccessKey = ""; // 密码
  const endpoint = ""; // minio上传地址
  const region = ""; //  AWS 服务端点 文档地址 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html
  const s3ForcePathStyle = true; // 是否强制使用 S3 对象的路径样式 URL。
  const signatureVersion = "v4"; //用于对请求进行签名的签名版本(覆盖 API 置)。可能的值: 'v2'|'v3'|'v4'
  return new AWS.S3({
    accessKeyId,
    secretAccessKey,
    endpoint,
    region,
    s3ForcePathStyle,
    signatureVersion,
  });
};

/**
 * @description 上传管理
 */
class UploadManage {
  #bucket; // 桶名
  #s3 = createS3(); // s3 实例
  #fileList = []; // 文件列表
  #events = []; // 事件响应
  #uploader = new Map(); // 上传实例

  /**
   * @param {{bucket:string;}} param0
   */
  constructor({ bucket }) {
    this.#bucket = bucket;
  }

  /**
   * @description
   * @param {FileList} fileList
   */
  setFileList(fileList) {
    this.#fileList = [];
    for (let index = 0; index < fileList.length; index++) {
      const file = fileList.item(index);
      if (file) {
        this.#fileList.push({
          originFile: file,
          status: FileStatus.waiting,
          percent: 0,
        });
      }
    }
  }

  /**
   * @description 监听事件
   * @param {"onSucess" | "onError" | "onFailed" | "onChange" | "onLoading" | "onProgress" | "onAbort"} event
   * @param {(e:any)=>void} listener
   */
  on(event, listener) {
    this.#events.push({
      listener,
      event,
    });
  }

  /**
   * @description 关闭事件
   * @param {"onSucess" | "onError" | "onFailed" | "onChange" | "onLoading" | "onProgress" | "onAbort"} event
   */
  off(event) {
    this.#events = this.#events.filter((item) => item.event !== event);
  }

  /**
   * @description 触发事件
   * @param {"onSucess" | "onError" | "onFailed" | "onChange" | "onLoading" | "onProgress" | "onAbort"} event
   * @param {any} data
   */
  #emit(event, data) {
    this.#events.forEach((item) => {
      if (item.event === event) {
        item.listener(data);
      }
    });
  }

  /**
   * @description 上传文件
   * @param {{originFile:File;status:string;percent:number;abort:()=>void|null;}} file
   */
  #upload(file) {
    const params = {
      Bucket: this.#bucket,
      Key: file.originFile.webkitRelativePath,
      Body: file.originFile,
      ContentType: file.originFile.type,
    };
    const uuid = getCryptoUuid();
    const uploader = this.#s3.upload(params);
    this.#uploader.set(uuid, uploader);
    file.abort = uploader.abort;

    uploader.on("httpUploadProgress", (p) => {
      file.percent = Number(((p.loaded / p.total) * 100).toFixed(0));
    });

    uploader.send((err, data) => {
      if (err) {
        file.status = FileStatus.failed;
        this.#emit("onFailed", err);
      } else {
        file.status = FileStatus.success;
        this.#emit("onSucess", data);
      }
      file.abort = null;
      this.#uploader.delete(uuid);
      this.#pushUpload();
    });
  }

  /**
   * @description 取消全部上传
   */
  abort() {
    this.#fileList.forEach((file) => {
      if (file.status === FileStatus.waiting) {
        file.status = FileStatus.cancel;
      }
    });
    for (const [_, uploader] of this.#uploader.entries()) {
      uploader.abort();
    }
    this.#uploader.clear();
  }

  /**
   * @description 推入上传
   */
  #pushUpload() {
    for (let index = 0; index < this.#fileList.length; index++) {
      const file = this.#fileList[index];
      if (file.status === FileStatus.waiting) {
        file.status = FileStatus.uploading;
        this.#upload(file);
        break;
      }
    }
  }

  /**
   * @description 开始上传
   */
  start() {
    if (this.#fileList.length === 0) {
      this.#emit("onError", "没有可上传的文件");
    } else {
      this.#fileList.slice(0, 3).forEach((file) => {
        file.status = FileStatus.uploading;
        this.#upload(file);
      });
    }
  }
}

const actions = {
  uploads: "uploads",
  abort: "abort",
};

// 检测有无S3
if (AWS && AWS.S3) {
  // 上传管理实例
  const uploadManage = new UploadManage({ bucket: "你的桶名" });

  // 上传成功
  uploadManage.on("onSucess", (e) => {
    console.log(e, "onSucess");
  });

  // 上传失败
  uploadManage.on("onFailed", (e) => {
    console.log(e, "onFailed");
  });

  // 监听通信
  onmessage = (e) => {
    const { action, payload } = e.data;

    switch (action) {
      case actions.uploads:
        uploadManage.setFileList(payload);
        uploadManage.start();
        break;
      case actions.abort:
        uploadManage.abort();
        break;
      default:
        break;
    }
  };
}

2. 启动项目

使用http-server

npx http-server

文献

AmazonS3
minio
Web_Workers_API

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值