起因
项目需要做一个上传功能,每次上传20个视频,不处理直接传的话会有一个阻塞问题。
因为一个网页最多同个域名只能有6个tcp连接。不控制并发同时传6个,但是用户后续的所有调其他接口的操作都会被挂起来。网络不好的情况下更是灾难,所以需要控制并发,一次最多只传3个。
正好又有这种类型的面试题,写个demo讲下原理。
后端代码
后端需要有基本的返回,随机延迟,随机错误,能跨域
const http = require('http')
http.createServer((req,res)=>{
const delay = parseInt(Math.random()*5000)
console.log(delay);
setTimeout(()=>{
const returnerror = Math.random()>0.8
// if(returnerror){
// res.writeHead(500,{
// // 'Content-Type': 'text/plain',
// 'Access-Control-Allow-Origin':'*',
// 'Access-Control-Allow-Headers':'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild',
// 'Access-Control-Allow-Methods':'GET',
// }).end('error,from '+req.url+' after '+delay+'ms')
// }else{
res.writeHead(200,{
// 'Content-Type': 'text/plain',
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Headers':'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild',
'Access-Control-Allow-Methods':'GET',
}).end('OK,from '+req.url+' after '+delay+'ms')
// }
},delay)
}).listen(3000,()=>{
console.log('服务启动在3000!');
})
前端代码
html主要为了动画的一个展示,就不细说了。
js分为两部分,一部分控制dom渲染具体看总结出给的源码地址,这个也不细说了,第二部分就是并发控制器
input数组里面是请求地址
const input = new Array(10).fill('http://localhost:3000/').map((item,index)=>`${item}${index+1}`)
基础版
并发控制主要是两个函数和一个promise队列
。
- 一个负责不断的
递归
往队列里面加promise。 - 另外一个负责生产promise,处理异步请求逻辑
function handleFetchQueue(input, max) {
const requestsQueue = []; // 请求队列
addReactive(requestsQueue) //对requestsQueue添加响应式,重写push,splice方法使其能同时改变页面上的队列dom。
let i = 0;
const req = (i)=>{ //产生一个promise请求 成功则删除队里中promise 再添加一个请求
return fetch(input[i]).then(res=>res.text()).then(res=>{
addEl(res) //结果渲染页面列表
const index = requestsQueue.findIndex(item=>item===req)
requestsQueue.splice(index,1)
checkAddReq()
})
}
const checkAddReq = ()=>{
if(i>=input.length) return // 请求数不能越界
if(requestsQueue.length+1 <= max) { // 并发不能越界
setTimeout(()=>{ //加延迟为了提高动画效果,实际不需要
requestsQueue.push(req(i++))
checkAddReq()
},50)
}
}
checkAddReq();
}
handleFetchQueue(input,3)
处理异常
需要考虑的问题是异常了怎么办。
异常对于并发控制也就是计数原理应该没有区别,只是需要额外再promise生成这里添加下业务逻辑即可。
我们把后端代码随机错误的注释打开
然后前端核心逻辑改一下
const req = (i)=>{
return fetch(input[i]).then(async(res)=>{
if(res.status===500){ //这里异步改一下,得让走catch
const text = await res.text()
throw new Error(text);
}else{
return res.text()
}
}).then(res=>addEl(res),error=>addEl(error,true))
.finally(()=>{ //无论成功失败并发逻辑一样,只是then中业务逻辑不同
const index = requestsQueue.findIndex(item=>item===req)
requestsQueue.splice(index,1)
checkAddReq()
})
}
总结
完整地址 https://github.com/fyy92/code...
- index.html 不考虑异步
- index1.html 考虑异常
- serve.js 后端代码
总结一下
- 并发控制关键在两个函数,一个控制生成业务Promise,一个迭代控制并发队列添加业务Promise。
- 对于异常,需要保证并发逻辑一样,不同的是业务逻辑的处理。
补充
最近发现一个库 asyncpool 仿写一下它
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
const results = await asyncPool(2, [1000, 5000, 3000, 2000], timeout);
// Call iterator (i = 1000)
// Call iterator (i = 5000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 1000 finishes 1秒后
// Call iterator (i = 3000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 3000 finishes 4秒后
// Call iterator (i = 2000)
// Itaration is complete, wait until running ones complete...
// 5000 finishes 5秒后
// 2000 finishes 6秒后
// Resolves, results are passed in given array order `[1000, 5000, 3000, 2000]`.
网上的版本一般用Promise.settle Promise.all
所以比如生成一个完整的Promse数组
而我们前面介绍的是分阶段去产生Promise
所以晚上的相当于把我们生成promise的操作再封一层函数,然后就可以用Promise.settle Promise.all。我们这里直接使用Promise.settle Promise.all原理还是按之前的思路去做
unction asyncPool(poolLimit, array, iteratorFn){
return new Promise((resolve,reject)=>{
const queue =[]
const result = []
let num = 0
let i = 0
const add = ()=>{
// console.log(i,queue.length)
if(i>=array.length) return
if(queue.length>=poolLimit) return
const p = iteratorFn(array[i]).then((res)=>{ //这里最好用finally 但是注意finally res为undefined
// console.log(i)
result.push(res)
queue.splice(queue.findIndex(i=>i===p),1)
add(i) //i已经加过了被return了的
num++
console.log(num)
if(num===array.length){
resolve(result)
}
})
queue.push(p)
add(++i)
}
add(0)
})
}
let oldtime = new Date().getTime()
const timeout = i => new Promise(resolve => setTimeout(() => {
resolve(i)
// console.log(i,new Date().getTime()-oldtime)
}, i));
asyncPool(2, [1000, 5000, 3000, 2000], timeout).then(res=>{
console.log(res) //1000 3000 5000 2000
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。