大文件上传实现-并发控制功能(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,下一篇文章,我们来看看相反的操作功能实现-大文件下载,敬请期待。

相关推荐
道爷我悟了1 小时前
Vue入门-指令学习-v-html
vue.js·学习·html
无咎.lsy1 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
工业互联网专业1 小时前
毕业设计选题:基于ssm+vue+uniapp的校园水电费管理小程序
vue.js·小程序·uni-app·毕业设计·ssm·源码·课程设计
计算机学姐2 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
twins35203 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky3 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
杨荧4 小时前
【JAVA开源】基于Vue和SpringBoot的洗衣店订单管理系统
java·开发语言·vue.js·spring boot·spring cloud·开源
Front思4 小时前
vue使用高德地图
javascript·vue.js·ecmascript
余生H7 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
花花鱼7 小时前
@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
vue.js