当前位置: 首页 > Web前端 > JavaScript

async异步编程工具

时间:2023-03-27 16:33:43 JavaScript

在es6中async的语法中,可以参考javaconcurrent包实现一些有趣的异步工具,辅助异步场景(一般指请求)的开发。由于js是单线程的,所以下面的实现都比java简单(抛开线程概念)。同时涉及到js的执行机制、宏任务、微任务、async、promise相关内容,需要提前获取。异步函数中的等待(wait),等待(相当于java线程中的阻塞)一段时间。实现代码:asyncfunctionwait(time=0){awaitnewPromise(resolve=>setTimeout(resolve,time));//避免翻译成returnawait,在某些safari版本会报错returnundefined;}模拟代码:(async()=>{console.time();awaitwait(1000);console.timeEnd();//output:default:1.002s})();Lock(锁)模拟java并发包中的Lock类实现锁操作。保证被同一个锁包围的异步代码在执行过程中,同时只执行一段代码。该锁不符合html5的异步锁接口,而是提供了java异步包中Lock接口的简单实现。推荐使用场景:在多个请求执行前,同时保证只执行一次token失效校验转换操作。实际代码:exporttypeResolve=(value:T|PromiseLike)=>void;exporttypeReject=(reason?:any)=>void;exportinterfaceFlatPromise{promise:承诺;解决:解决;reject:Reject;};interfaceKey{key:number,resolve:Resolve,};/***创建一个持平的promise*@returnsPrmise*/functionflatPromise():FlatPromise{const结果:任何={};constpromise=newPromise((resolve,reject)=>{result.resolve=resolve;result.reject=reject;});结果.承诺=承诺;返回结果为FlatPromise;}classLock{keys:Key[]=[];有锁:布尔值=假;idCount:数字=0;构造函数(){this.keys=[];this.hasLock=false;这个.idCount=0;}_pushKey(resolve:Resolve){this.idCount+=1;constkey:Key={key:this.idCount,resolve,};this.keys.push(key);返回键;}_remveKey(key:Key){constindex=this.keys.findIndex(item=>item.key===key.key);if(index>=0){this.keys.splice(index,1);}}/***获取锁。*如果当前锁已经被锁定,则阻塞当前操作*/asynclock(){if(this.keys.length||this.hasLock){const{promise,resolve}=flatPromise();这个._pushKey(解决);等待承诺;返回空值;}this.hasLock=true;返回空值;}/***尝试获取锁。*如果这个函数没有指定有效时间,它会立即返回一个结果:如果获得了锁,则为true,否则为false。*如果指定有效时间(time=0有效),则返回一个promise对象,该对象返回的结果为是否获取到锁*@paramtime最长等待时间*/tryLock(time?:number){if(time===undefined||Number.isNaN(Math.floor(time))||time<0){if(this.hasLock){returnfalse;}这个.lock();返回Promise.resolve(true);}if(!this.hasLock&&!this.keys.length){this.hasLock=true;返回Promise.resolve(true);}constasyncFn=async()=&g吨;{const{promise,resolve:res}=flatPromise();constkey=this._pushKey(res);setTimeout(()=>{this._removeKey(key);key.resolve(false);},时间);constisTimeout=等待承诺;返回isTimeout!==false;};返回asyncFn();}asynclockFn(asyncFn:()=>Promise){awaitthis.lock();尝试{awaitasyncFn();}最后{this.unlock();}}/***释放锁*/unlock(){if(this.keys.length===0&&this.hasLock===true){this.hasLock=false;返回;}如果(this.keys.length===0){返回;}constindex=Math.floor(Math.random()*this.keys.length);constkey=this.keys[索引];这个._removeKey(键);key.resolve(未定义);}toString(){返回`${this.keys.length}-${this.hasLock}`;}}模拟使用代码:functiondelay(callback:()=>void,time:number){returnnewPromise((resolve)=>setTimeout(()=>{callback();resolve(undefined);},time));}(async()=>{constlock=newLock();constsyncResult:string[]=[];constunSyncResult:string[]=[];constwithLockAsync=async()=>{awaitlock.lock();awaitdelay(()=>{syncResult.push('1');},Math.random()*10);awaitdelay(()=>{syncResult.push('2');},Math.random()*10);awaitdelay(()=>{syncResult.push('3');},Math.random()*10);lock.unlock();};constwithoutLockAsync=async()=>{awaitdelay(()=>{unSyncResult.push('1');},Math.random()*3);awaitdelay(()=>{unSyncResult.push('2');},Math.random()*3);awaitdelay(()=>{unSyncResult.push('3');},Math.random()*3);};consttaskList=[];for(leti=0;i<10;i+=1){taskList.push(withLockAsync(),withoutLockAsync());}awaitPromise.all(taskList);//output1,2,3,1,2,3...//证明withLockAsync函数中只有一个代码同时执行,不会被打断Chaosconsole.log(syncResult);//输出值不一定按照1,2,3,1,2,3...//证明在普通的async函数中,多个await代码会被打乱console.log(unSyncResult);})();Semaphore(信号量)Semaphore(信号量)用于控制同时访问特定资源的线程数。它协调每个线程以确保合理使用公共资源。推荐使用场景:通用用于流量控制,尤其是公共资源有限的应用场景。比如控制同时请求的连接数,假设浏览器最大连接请求数为10,多个异步并发请求可以使用Semaphore来控制请求的最大异步执行数为10。简单的实现代码:classSemaphore{constructor(permits){this.permits=permits;这个.execCount=0;this.waitTaskList=[];}asyncacquire(){constthat=this;这个.execCount+=1;if(this.execCount<=this.permits){//为了保证调用顺序执行//如果有等待的,则先执行等待的,当前挂起的//如果没有,则快速通过通过if(that.waitTaskList.length!==0){constwaitTask=this.waitTaskList.pop();等待任务();awaitnewPromise((resolve)=>{that.waitTaskList.push(resolve);});}返回;}awaitnewPromise((resolve)=>{that.waitTaskList.push(resolve);});}release(){this.execCount-=1;如果(this.execCount<0){this.execCount=0;}if(this.waitTaskList.length===0){返回;}constwaitTask=this.waitTaskList.pop();等待任务();}}在复杂页面模拟复杂请求场景:(async()=>{constsemaphore=newSemaphore(5);letdoCount=0;letcurrntCount=0;constrequest=async(id)=>{awaitsemaphore.acquire();当前计数++;console.log(`执行请求${id},执行次数${currntCount}`);等待新的承诺(解决=>setTimeout(解决,Math.random()*1000+500));信号量.release();当前计数--;做计数++;console.log(`执行请求${id}结束,已执行${doCount}次`);};constarr=newArray(10).fill(1);setTimeout(()=>{//依次执行10个请求arr.forEach((_,index)=>{request(`1-${index}`);});},300)//随机触发10请求让索引=0;consttimerId=setInterval(()=>{if(index>9){clearInterval(timerId);return;}request(`2-${index}`);index++;},Math.random()*300+300);//同时执行10个请求awaitPromise.all(arr.map(async(_,index)=>{awaitrequest(`3-${index}`);}));//等待以上内容全部执行完毕,资源释放后,执行4次请求setTimeout(()=>{constlastArr=newArray(4).fill(1);lastArr.forEach((_,index)=>{请求(`4-${index}`);});},5000);})();执行结果:执行请求3-0,正在执行的个数1执行请求3-1,正在执行的个数2执行请求3-2,正在执行的个数3执行请求3-3,正在执行的个数4执行请求3-4,编号5正在执行执行请求3-2完成,执行1次执行请求2-1,编号5正在执行执行请求3-3结束,已执行2次执行请求2-0,执行次数为5...执行请求3-8已完成,已执行29次执行请求3-6已完成,已执行30次执行请求4-0,正在执行的个数1执行请求4-1,已执行的个数executed2执行请求4-2,正在执行的个数3执行请求4-3,正在执行的个数4执行请求4-3结束,已经执行了31个执行请求4-0结束,已经执行了32个执行请求4-2结束,已经执行了33个执行请求4-1结束,CountDownLatch(countdownlock)和CyclicBarrier(cyclicbarrier)执行了34次在es的async中,一般场景下的CountDownLatch可以直接使用Promise.all代替使用场景:复杂的Promise.all需求场景支持在离散多个异步函数中灵活使用(我还没遇到过),这样才能去掉Promise.all,使用的时候需要一个promise可迭代的输入。代码实现:classCountDownLatch{constructor(count){this.count=count;this.waitTaskList=[];}countDown(){this.count--;if(this.count<=0){this.waitTaskList.forEach(task=>task());}}//避免使用关键字,所以命名与java不同asyncawaitExec(){if(this.count<=0){return;}常量=这个;awaitnewPromise((resolve)=>{that.waitTaskList.push(resolve);});}}模拟代码:(async()=>{constcountDownLatch=newCountDownLatch(10);constrequest=async(id)=>{console.log(`Executerequest${id}`);awaitnewPromise(resolve=>setTimeout(resolve,Math.random()*1000+500));console.log(`Executerequest${id}end`);countDownLatch.countDown();};consttask=newArray(10).fill(1);//后续代码等价于//awaitPromise.all(task.map(async(_,index)=>{//awaitrequest(index);//}));task.forEach((_1,index)=>{request(index);});awaitcountDownLatch.awaitExec();console.log('执行完成');})();CyclicBarrier抛开线程相关概念后,核心功能是一个可复用的CountDownLatch,这里不做实现