你可能不知道的并发方法

738 阅读2分钟

promise控制并发

排队的办法

假如去饭堂打菜,只有10个窗口,但学生有上百人,应该怎么办呢?假设学生打菜是IO操作时间不等,我们可以用递归等上一个人做完再去做下一个人.我一开始想到的办法是reduce.

let arr =[1,2,3,4,5,6,7,8,9,10] //假设有10个学生
arr.reduce((last,now,index)=>{
    last.then(()=>{
        setTimout(()=>{
            resolve(now)
        },Math,random()*1000)     
    })
},Promise.resolve())

这种办法可以控制上一个学生打完菜才到下一个学生,但这不符合要求,我们是有三个窗口,上面的情况相当于只有一个窗口.因此,我们应该自己实现一个递归,在递归里面判断当前的任务数量.

function delay(){
    //模拟异步操作
    return new Promise((resolve,reject)=>{
		setTimeout(function(){
            resolve()
        },Math.random()*10000)
		
	})    
}

function recusive(tasks,taskLimit,handler){
    let num = 0,index=0;
    function a(){
		if(num<taskLimit&&index<tasks.length){
            Promise.resolve().then(()=>handler(tasks[index++])).then(()=>{   
                console.log('当前并发数:'+num);
                num--;
                a();
            });
            num++;  
            
        }
	}
    //任务初始化分配
    for(let i=0;i<taskLimit;i++){
		a();
	}
}

//测试
recusive(arr,3,(item)=>{ console.log(item); return delay() })
/*
 1
 2
 3
 当前并发数:3
 4
 当前并发数:3
 5
 当前并发数:3
 6
 当前并发数:3
 7
 当前并发数:3
 8
 当前并发数:3
 9
 当前并发数:3
 10
 当前并发数:3
 当前并发数:2
 当前并发数:1

*/

一开始先同时执行三个任务,有一个退出以后就接替下一个,数量一直为3

async await的方法

async await是promise的语法糖,在async 函数里面的await能够堵塞代码等待代码执行完毕后才进行下一步,所以可以替换then来保持队列的长度一直为常数值.

function recusive(tasks,taskLimit,handle){
    let num=0,index=0,lock=[];
    function block(){
        //堵塞
        return new Promise((resolve,reject)=>{
            console.log('堵塞')
            lock.push(resolve)
        })
    }
    function release(){
        //释放
         console.log('释放')
        lock.length&&lock.shift()()
    }
    async function a(){
        if(num>=taskLimit){
            await block();   
        }
        if(index<tasks.length){
            num++;
            console.log('当前并发数:'+num);
            await handle(tasks[index++]);
            release();
            num--;
        }       
    }
    for(let i=0;i<tasks.length;i++)a();
}

promise.race的办法

promise.race可以返回promise数组中第一个执行完成的promise对象的结果,只要填满它race的时候再添加新的promise,删除完成的promise就行了.

问题是怎么找到已经完成的promise对象,我们可以在定义的时候把某个属性作为判断,就用永远不会一样的东西最方便,时间就不会相同.

function recusive(tasks, taskLimit, handle) {
  let index = 0,
    num = 0,
    promisesArr = [];
  function a(tasks, taskLimit, handle) {
    console.log('任务数量:' + promisesArr.length)
    let id, p;
    if (index <= tasks.length) {   
      if (promisesArr.length < taskLimit) {
        id = new Date().getTime() //唯一标识符
        p = new Promise((resolve, reject) => { //p 之所以放在里面是不想先执行了p
          console.log(`${index}开始执行`)
          handle(tasks[index++])
            .then(() => {
              resolve(id)
            })
        })
        p.id = id
        p.then(id => {
          promisesArr=promisesArr.filter(item => item.id != id)
        })
        num++;
        promisesArr.push(p)
        a(tasks, taskLimit, handle)
      } else {
        // 超过并发数 用race去掉旧的换新的
        Promise.race(promisesArr).then(() => {
          id = new Date().getTime() //唯一标识符
          p = new Promise((resolve, reject) => {
            console.log(`${index}开始执行`)
            handle(tasks[index++])
              .then(() => {
                resolve(id)
              })
          })
          p.id = id
          promisesArr.push(p)
          
          return p.then(id => {
            promisesArr=promisesArr.filter(item => item.id != id)
          })
        }).then(()=>{
          a(tasks, taskLimit, handle)
        })
      }
    }
  }
  for (let i = 0; i <3; i++) {
    a(tasks, taskLimit, handle)
   
  }
}

let arr = Array.from({
  length: 100
}, (item, i) => i)
//测试用例
recusive(arr, 3, (item) => new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log(item);
    resolve(item)
  }, Math.random() * 10000)
}))