5分鐘搞定Promise控制併發

Runningfyy發表於2022-01-21

Jan-21-2022 12-13-30.gif

起因

專案需要做一個上傳功能,每次上傳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}`)

image.png

基礎版

併發控制主要是兩個函式和一個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。
  • 對於異常,需要保證併發邏輯一樣,不同的是業務邏輯的處理。

相關文章