大文件上传实现-并发控制功能(Vue+Express)

前言

大文件上传实现专栏的前三篇文章,带大家逐步实现了大文件上传的切片上传、断点续传、秒传等前后端功能。如果对大文件上传的原理不了解,可以先看看前面三篇文章。

需求分析

前面的文章中,例子中用的大文件大小是1.5G, 而切片大小固定是100M(实际的大小,可能还得根据网络带宽、服务器性能等其他因素考虑,与服务器协商确定,文章就不展开了),那么切片数量就有16个了,而我们注意到,同一个域名下面,最多并发6个请求,多余的得排队了。如下图,虽然是用了Promise.all()来同时进行16个请求,但实际是最多六个。

那么问题来了,如果页面有其他要发送请求的业务,此时会被排到最后了,这样子不好。那么就需要进行并发控制了,可以控制大文件上传并发的数量,不至于完全占用网络资源,阻碍其他业务。

异步并发控制

异步并发控制,套到大文件上传场景,具体到这个项目,有15个文件切片上传,期待的是能够控制同时上传的请求数量,比如三个或者4个。

完成这种控制功能的库,我了解到的有 async-pool

怎么用呢?输入是三个参数,第一个是并发量,第二个是待处理的数据数组,第三个是处理数据的异步函数。

参考该库的源码,来实现一下:

js 复制代码
async function asyncPool(limit, arr, handleFn) {
  // 用来装所有的promise
  const ret = [];
  // 用来装正在执行的promise
  const executing = new Set();
  // 循环arr, 用 Promise.race 来执行
  for (let item of arr) {
    const p = Promise.resolve()
      .then(() => handleFn(item))
      .finally(() => executing.delete(p)); // 执行完后删除

    // 放到队列中
    ret.push(p);
    executing.add(p);

    // 当正在执行的promise数量达到limit时,进行并发控制,等待一个promise完成再继续循环
    if (executing.size >= limit) {
      await Promise.race(executing);
    }
  }
  return Promise.all(ret);
}

注意是用了ES7的语法,才看起来思路这么清晰。

测试代码,处理函数是延迟一秒resolve,用来并发控制,那么期待的结果是:每隔一秒打印两个数据,直到打印完所有数据。

js 复制代码
const handleFn = (data) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log("data: ", data);
      resolve(data);
    }, 1000);
  });
};
const results = await asyncPool(2, [1, 2, 3, 4, 5, 6, 7], handleFn);
console.log("results: ", results);

看看效果:

没问题,那么整合到的上传文件流程中吧。

先把原本循环发送请求的部分代码改一下,包成函数,而不是立即请求:

diff 复制代码
- const requests = chunks.map((chunkInfo) => {
+ const requestFns = chunks.map((chunkInfo) => {
+   const requestFn = () => {
      const cancelToken = axios.CancelToken.source();
      cancelTokens.push(cancelToken);
      // 查找是否有上传过的切片
      const uploadedChunk = uploadedChunks.find(
        (item) => item.chunkFilename === chunkInfo.chunkFilename
      );
      ...
+    };
+    return requestFn;
  });

使用 asyncPool 函数来控制

diff 复制代码
- await Promise.all(requests);
+ await asyncPool(3, requestFns, (requestFn) => requestFn());

来看看效果:

🥳 如愿,效果是每次三个请求,那么就达成了我们期待的效果了,优化完成。

补充

上面asyncPool的实现,用的ES7的语法,来实现一版兼容性好一点的,采用了异步递归的方式:

js 复制代码
function asyncPool(limit, arr, handleFn) {
  // 用来装所有的promise
  const ret = [];
  // 用来装正在执行的promise
  const executing = new Set();

  let i = 0;
  const loop = () => {
    // 递归终止条件
    if (i === arr.length) {
      return Promise.resolve();
    }
    const p = Promise.resolve()
      .then(() => handleFn(arr[i++]))
      .finally(() => executing.delete(p)); // 执行完后删除
    ret.push(p);
    executing.add(p);

    // 当正在执行的promise数量达到limit时,进行并发控制,等待一个promise完成再继续循环
    let r = Promise.resolve();
    if (executing.size >= limit) {
      r = Promise.race(executing);
    }
    return r.then(loop);
  };

  return loop().then(() => Promise.all(ret));
}

测试方式一样,这种实现绕一点,但实现效果是一样的。

总结

这篇文章接着文件上传实现的最终功能,提出了一个问题,多个切片并发上传,会把网络资源全部占用了,如果有其他请求,那么得排队,这不是期待的效果,期待的效果是能够控制上传的并发数量,给其他业务保留一点网络请求资源。然后参考了 asyncPool 的实现,写了一版并整合到上传的代码中,实现了大文件上传的并发控制功能。可以把并发数量交给用户设置,也可以设定一个合适的默认值(<6),如果超过6,那跟没做优化的效果一样,大可不必。

代码放到gitee,下一篇文章,我们来看看相反的操作功能实现-大文件下载,敬请期待。

相关推荐
开心工作室_kaic1 小时前
ssm111基于MVC的舞蹈网站的设计与实现+vue(论文+源码)_kaic
前端·vue.js·mvc
bug爱好者2 小时前
如何解决sourcetree 一打开就闪退问题
前端·javascript·vue.js
迂 幵2 小时前
vue el-table 超出隐藏移入弹窗显示
javascript·vue.js·elementui
上趣工作室2 小时前
vue2在el-dialog打开的时候使该el-dialog中的某个输入框获得焦点方法总结
前端·javascript·vue.js
家里有只小肥猫2 小时前
el-tree 父节点隐藏
前端·javascript·vue.js
_xaboy4 小时前
开源项目低代码表单设计器FcDesigner扩展自定义的容器组件.例如col
vue.js·低代码·开源·动态表单·formcreate·低代码表单·可视化表单设计器
_xaboy4 小时前
开源项目低代码表单设计器FcDesigner扩展自定义组件
vue.js·低代码·开源·动态表单·formcreate·可视化表单设计器
mez_Blog4 小时前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪4 小时前
vue文本高亮处理
前端·javascript·vue.js
paopaokaka_luck4 小时前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法