1

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

cause

The project needs to do an upload function, upload 20 videos at a time, and there will be a blocking problem if the direct upload is not processed.
Because a webpage can only have 6 tcp connections for the same domain name at most. It does not control the concurrent transmission of 6 at the same time, but all subsequent operations of the user to call other interfaces will be suspended. It is even a disaster when the network is not good, so it is necessary to control the concurrency, and only transfer at most 3 at a time.
It happens that there is another interview question of this type, so write a demo to explain the principle.

backend code

The backend needs to have basic returns, random delays, random errors, and cross-domain

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!');
})

front-end code

HTML is mainly for a display of animation, so I won't go into details.
js is divided into two parts, one part controls dom rendering, see the source code address summed up, this is not detailed, the second part is the concurrent controller
The input array is the request address

const input = new Array(10).fill('http://localhost:3000/').map((item,index)=>`${item}${index+1}`)

image.png

Basic Edition

Concurrency control is mainly two functions and a promise queue.

  • One is responsible for the constant recursively adding promises to the queue.
  • The other is responsible for producing promises and handling asynchronous request logic
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)

Handling exceptions

The question to be considered is what to do if there is an exception.
There should be no difference between exceptions for concurrency control, that is, the counting principle, just need to generate additional promises and add business logic here.
We turn on the comments of random errors in the backend code
Then change the front-end core logic

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()
            })
        }

Summarize

Full address https://github.com/fyy92/codeTraining/tree/master/21.Promise

  • index.html does not consider async
  • index1.html considers exceptions
  • serve.js backend code

in conclusion

  • The key to concurrency control lies in two functions, one to control the generation of business promises, and the other to iteratively control the concurrent queue to add business promises.
  • For exceptions, it is necessary to ensure that the concurrency logic is the same, but the difference is the processing of business logic.

Replenish

Recently I found a library asyncpool to imitate it

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]`.

The online version generally uses Promise.settle Promise.all
So for example generate a full Promse array
And what we introduced earlier is to generate Promise in stages
So at night, it is equivalent to encapsulating a layer of functions for the operation we generate promises, and then we can use Promise.settle Promise.all.
We use the principle of Promise.settle Promise.all directly here or do it according to the previous idea

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
})

Runningfyy
1.3k 声望661 粉丝