赞
踩
在上一篇文章中,我们讲解了分片上传的实现方式。在讲解断点续传之前,我要把上篇文章中留下的问题讲解一下。读过上一篇文章的小伙伴们都知道,对于分片上传来说,它的传输方式分为2种,一种是按顺序传输
,一种是并发的方式传输
。
上一篇里,我们只讲解了第一种方式,第二种方式并没有过多的去讲解。接下来,小编会带着大家,一步步拆解这个内容。大家系好安全带,面试题要来了。
大家肯定都刷到过这样的面试题:给你100张图片,如何实现并发上传
?
首先我们要知道,浏览器一次性能发送多少个请求?答案是6个
。那也就是说,我们需要设计一个流程,每次并发6个,直到所有的请求都完成响应。
那如何实现并发呢?
Promise似乎提供了一些方法,比如 Promise.allSettled
、Promise.all
、Promise.race
。
那这个时候,面试题又来了,它三的区别是什么
?在这个场景里,使用哪个API更合理?
共同点:参数都是由Promise实例组成的数组。
不同点:返回值不同。Promise.allSettled返回的是所有实例的状态数组。all与race返回的都是对象,最先成功或者失败的对象。
在我们这个场景里,我们优先选择 Promise.allSettled。知道了并发的方式还远远不够,假设我们有10个地址,一次并发发送6个请求,所以需要2次并发6个请求。那这个2次该如何得知呢?最简单也最实用的方案就是通过 Array.splice
方法 来切割数组。因为splice进行删除操作时会影响到原数组,也就是原数组里也没有了被删除的元素。那我们就可以通过while循环来判断原数组是否还有剩余元素。没有剩余元素意味着并发请求结束了。
这个时候一个知识点也过来了。一起复习一下,slice与splice有什么区别?
讲了这么多理论,也是时候动手实践一波了:
let arr = [ {name: 1}, {name: 2}, {name: 3}, {name: 4}, {name: 5}, {name: 6}, {name: 7}, {name: 8}, {name: 9}, {name: 10}, {name: 11}, ]; async function ax(){ while(arr.length > 0){ let newArr = arr.splice(0, 6); let result = await Promise.allSettled( newArr.map(item => { return new Promise((resolve, reject) => { setTimeout( () => { return resolve(item.name) }, 1000 ) }) }) ); console.log('result是撒谎:', result); } } ax();
执行一下上述代码,我们会发现,按照了我们的预期,过了1s,控制台同时打印了1-6。再过了1s,控制台同时的打印了剩余元素。
那我们把这个思想转换到 分片上传的代码里。
// Video组件里的其他内容均不变,有不清晰的请看前2篇文章 ========== // 并发分片上传 concurrencyUploadChunkFile = async () => { let self = this; // 定义每块体积大小为5MB let chunk_size = 5 * 1024 * 1024; // 当前上传的文件对象 let fileObj = this.state.fileObj; // 当前上传的文件对象的体积 let allSize = this.state.fileObj.size; // 当前文件被切割的总分片数量 let allChunkCount = Math.ceil(allSize / chunk_size); // chunk文件集合 let chunkArr = []; for (let index = 0; index < allChunkCount; index++){ let startIndex = index * chunk_size; let endIndex = Math.min(startIndex + chunk_size, allSize); chunkArr.push({ data: fileObj.slice( index * chunk_size, endIndex ), filename: `chunk-${index}`, chunkIndex: index }); } // 开始并发分片上传 while(chunkArr.length > 0){ let reqArr = chunkArr.splice(0, 6); let reqArrFactory = reqArr.map( item => { return new Promise( (resolve, reject) => { let result = this.uploadChunkReq(item.data, item.chunkIndex, 'chunk'); return resolve(result); } ) } ) let resArr = await Promise.allSettled(reqArrFactory); console.log('6个请求的响应如何:', resArr); } // 合并请求 // this.mergeChunk(); render(){ return <div> <button onClick={this.concurrencyUploadChunkFile}>并发上传</button> </div> } }
在上述代码中,我们定义了分片的体积为5M,此时假如我们分片上传了一个61M的视频,根据计算,会得到13个分片,如果我们控制网速,能明显的看到分层才对。因为毕竟发了3波,每波都是发送6个请求,每波之间都是按顺序的。就像下面这样:
并发上传分片到这里就结束啦。但如果想扩展的话,其实还有很多考题,就拿并发来举例子,大家可能还会听到一些考题,比如实现并发的方法有哪些?
、Promise.allSetlled是如何实现的
等等。
我想说的是,大家不要去刻意背这些东西,因为这些题的本质就一个,就是js异步对象的运行机制。当你深入理解了就会发现,真的是举一反三。
直到目前为止,大家有没有发现,我们进展的过于顺利,如果在上传分片的过程中出现了失误,我们应该怎么办?
断点续传是一个防错机制。它在大文件上传失败时,不必重新来过,从断掉的地方重新上传即可。
如何实现呢?很简单,从哪个分片断掉,就从哪块上传呗。
但是大家也会发现,即使是最简单的场景,根据每个项目实现分片上传的方式不同,断点续传的方法也不尽一样。因为断点续传的方式不一样,所以导致后端实现合并分片的方式也会随之改变
。举个例子:
假如,我们有13个分片,序号分为是1-13,先并发1-6,此时我们发现分片6失败了,然后我们维护一个失败分片的数组,将分片6 push进去。接着并发 7 -12,此时都成功了。最后发送13,也成功了。因为分片6失败了,所以我们还需要再发送分片6,最后发现分片6也成功了。但是对于后端来说,在合并分片的时候就需要新增排序逻辑了,因为分片的序号对不上了。大家懂我的意思吧。
这里我们来新增前端处理断点续传的逻辑。逻辑如下:
// 其他内容不变 ======= // 并发上传分片 concurrencyUploadChunkFile = async () => { let self = this; // 定义每块体积大小为5MB let chunk_size = 5 * 1024 * 1024; // 当前上传的文件对象 let fileObj = this.state.fileObj; // 当前上传的文件对象的体积 let allSize = this.state.fileObj.size; let allChunkCount = Math.ceil(allSize / chunk_size); // 失败的分片集合(新增++++++++++++++++++++++++++++) let failedChunkArr = []; // chunk文件集合 let chunkArr = []; for (let index = 0; index < allChunkCount; index++){ let startIndex = index * chunk_size; let endIndex = Math.min(startIndex + chunk_size, allSize); chunkArr.push({ data: fileObj.slice( index * chunk_size, endIndex ), filename: `chunk-${index}`, chunkIndex: index }); } // 开始分片上传(update++++++++++++++++++++++++++++++++++++++++) while(chunkArr.length > 0 || failedChunkArr.length > 0){ let reqArr = chunkArr.length > 0 ? chunkArr.splice(0, 6) : failedChunkArr.splice(0, 6); let reqArrFactory = reqArr.map( item => { return new Promise( (resolve, reject) => { let result = this.uploadChunkReq(item.data, item.chunkIndex, 'chunk'); if (result?.code != 200){ failedChunkArr.push(item); } return resolve(result); } ) } ) let resArr = await Promise.allSettled(reqArrFactory); console.log('6个请求的响应如何:', resArr); } // 合并请求 // this.mergeChunk(); }
好啦,本篇文章到这里就结束啦,如果上述过程中有明显的错误,欢迎大家指正,希望我讲的对大家有帮助,那么我们下期再见啦,拜拜~~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。