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

相关推荐
web1508541593514 分钟前
vue 集成 webrtc-streamer 播放视频流 - 解决阿里云内外网访问视频流问题
vue.js·阿里云·webrtc
一个处女座的程序猿O(∩_∩)O3 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
迷糊的『迷』3 小时前
vue-axios+springboot实现文件流下载
vue.js·spring boot
web135085886353 小时前
uniapp小程序使用webview 嵌套 vue 项目
vue.js·小程序·uni-app
陈大爷(有低保)3 小时前
uniapp小案例---趣味打字坤
前端·javascript·vue.js
cronaldo913 小时前
研发效能DevOps: Vite 使用 Element Plus
vue.js·vue·devops
百罹鸟4 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
Java_慈祥4 小时前
慈様や 前端学习导航👩🏻‍🚀🚀
前端·javascript·vue.js
编程百晓君6 小时前
一文解释清楚OpenHarmony面向全场景的分布式操作系统
vue.js
暴富的Tdy6 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js