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

相关推荐
速易达网络6 小时前
RuoYi、Vue CLI 和 uni-app 结合构建跨端全家桶方案
javascript·vue.js·低代码
lyj1689977 小时前
vue-i18n+vscode+vue 多语言使用
前端·vue.js·vscode
我在北京coding10 小时前
TypeError: Cannot read properties of undefined (reading ‘queryComponents‘)
前端·javascript·vue.js
海天胜景11 小时前
vue3 获取选中的el-table行数据
javascript·vue.js·elementui
翻滚吧键盘11 小时前
vue绑定一个返回对象的计算属性
前端·javascript·vue.js
乆夨(jiuze)12 小时前
记录H5内嵌到flutter App的一个问题,引发后面使用fastClick,引发后面input输入框单击无效问题。。。
前端·javascript·vue.js
小彭努力中12 小时前
141.在 Vue 3 中使用 OpenLayers Link 交互:把地图中心点 / 缩放级别 / 旋转角度实时写进 URL,并同步解析显示
前端·javascript·vue.js·交互
xiguolangzi13 小时前
vue3+element-plus el-table列的显隐、列宽 持久化
前端·javascript·vue.js
大猩猩X13 小时前
vxe-upload vue 实现附件上传、手动批量上传附件的方式
vue.js·vxe-ui
come1123414 小时前
Vue 响应式数据传递:ref、reactive 与 Provide/Inject 完全指南
前端·javascript·vue.js