起因
專案需要做一個上傳功能,每次上傳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。
- 對於異常,需要保證併發邏輯一樣,不同的是業務邏輯的處理。