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

ChetuCub的自我修养-[ES6]异步函数管理方案解析

时间:2023-04-02 19:53:21 HTML

前言业务开发中经常会用到异步函数。下面简单分析一下异步函数的优缺点及其各种解决方案:优点:可以大大提高程序并发业务逻辑的能力。缺点:异步函数的编写方式和代码执行逻辑非常不直观,回调函数的方式也不符合人的线性思维。异步函数的执行流程通常难以管理,难以部署异步函数的错误处理机制。异步函数存在解决方案。于是就有了各种异步处理方案,比如原生回调函数promise/A+async/await(generator);业务场景,但是这些方案能解决什么问题才是我们关心的,其实如果把业务场景抽象出来,开发过程中异步函数的管理可以抽象成如下需求。比如有异步函数f1、f2、f3:对f1、f2、f3的执行顺序没有要求。它们的执行结果互不依赖,无所谓谁先完成谁后完成。f1、f2、f3之间的执行顺序没有要求。他们的执行结果互不依赖,谁先完成并不重要。但是有一个函数f4,它必须等到f1、f2、f3执行完才能执行。f1、f2、f3之间对执行顺序有要求,必须满足f1->f2->f3的执行顺序。下面简单介绍一下,针对不同的业务场景,每种解决方案可以解决哪些问题?需求1f1、f2、f3的执行顺序没有要求,即它们的执行结果不相互依赖。我们可以写成下面的形式f1(function(){});f2(函数(){});f3(函数(){});...需求2对f1、f2、f3的执行顺序没有要求,即它们各自的执行结果不互斥依赖,但是有一个函数f4,需要等到函数f1,f2和f3都已执行,然后才能执行解决方案:`Maintainacounter`。f1,f2,f3的执行顺序无所谓,但是对于f1,f2,在f3的每个完成的回调中,需要判断这三个函数是否已经完成(通过计数判断),如果都完成了完成,然后执行f4。其实Node的三方异步管理模块EventProxy,以及promise的promise.all的实现,都是使用这种方式来管理异步功能。(函数(){让计数=0;functionhandler(){if(count==3){f4();}}f1(function(){count++;handler();});f2(function(){count++;handler();});f3(function(){count++;handler();});}()需求3对于异步函数f1,f2,f3,我要保证它们的执行顺序是f1->f2->f3的顺序(即f1执行成功就调用f2,如果f2是执行成功,调用f3)3.1按照最原始的方法,可以写成回调嵌套的形式如下。即用f2作为f1的回调,f3作为f3的回调。依次嵌套满足f1->f2->f3的调用形式。这种方式虽然可以满足要求,但同时也存在很多问题:回调层级太深,不易调试。最简单的情况,假设不考虑f1、f2、f3的错误在这种情况下(即f1、f2、f3都正确执行),函数的执行流程大致是这样的:f1(function(){f2(function(){f3(function(){...})})})其实一般来说,考虑到每个异步函数都有可能出错的分支,真正的执行流程应该是这样的(这只是三层回调嵌套,代码完全乱七八糟,无法阅读:f1(function(){if(err){//f1errhandler}else{f2(function(){if(err){//f2错误处理程序}else{f3(function(){if(err){//f2errhandler}else{...}})}})}})3.2为了解决深度嵌套的问题,有promise这样的解决方案。这个规则逻辑比较清晰,也比较容易理解,但是需要做一点准备工作。也就是说,必须先将异步函数f1、f2、f3封装成一个promise规范。这里以f1为例(f2、f3同理)。functionf1(){varpromiseObj=newPromise(function(resolve,reject){//f1的具体功能代码实现...if(f1err){//iff1执行失败reject(failValue);}else{//iff1执行成功resolve(successValue);}})returnpromiseObj;}准备工作做好了,我们来看一下f1()的具体实现.then(functionsuc(){returnf2()},functionfail(){/*f1errhandler*/}).then(functionsuc(){returnf3()},functionfail(){/*f2errhandler*/}).then(functionsuc(){},functionfail(){/*f3errhandler*/})很简单分析接下来,f1()执行完成后,会返回一个promise对象,then会捕获到这个promise对象。如果promise对象的状态是resolve状态,那么会调用then的第一个参数,也就是成功回调。如果promise对象的状态是Inreject状态,那么会调用then的第二个参数,也就是失败回调。如果f1执行成功,then中的success回调suc中会调用f2(),f2()返回一个promise对象,会被下载A然后capture...等等。如果f1执行失败,then的失败回调fail中会调用你写的errhandler句柄,然后return跳出整个执行链。我们可以看到,promise的语法实际上是深入嵌入的。set的逻辑通过then的处理摊销。在这个语法规则下,f1->f2->f3的执行顺序一目了然。当然,它还是有缺点的,就像前面说的,它必须做一些准备工作,就是异步函数需要封装成一个promise规范。另外,它还有一堆then,看起来有点晕。3.3由于我们也觉得promises有点麻烦,所以只能试试es7的async/await。听说async/awaitawait+promise是管理异步回调的终极方案。首先,让我们澄清一下try/catch的概念。当一个代码片段,我们不确定它是否能执行成功,我们会使用try/catch来处理。当fun函数自上而下执行时,会进入最开始的try{}块,开始执行这段代码片段。一旦try{}块内的某段代码没有被正确执行,try{}块内的代码将不再执行,而是立即跳出try{}块,同时抛出异常,它将被catch(){}捕获。catch{}块中的代码将开始执行。我们假设code2是错误的,整个函数的执行顺序是code0->code1->code2->code4->code5;如果try{}块内的代码片段都被正确执行,则不会进入catch{}的错误处理流程。此时整个函数内部的执行顺序是代码0->代码1->代码2->代码3->代码5;functionfun(){/*code0*/try{/*代码1*//*代码2*//*代码3*/}catch(err){/*代码4*/}/*代码5*/}fun();对应于async也是如此。async函数有一个特性,它的await可以监视一个promise对象。如果被监控的promise对象处于正确的resolve状态,那么await语句就相当于被正确执行了,不会进入catch{}过程。但是如果被监控的promise是reject错误状态,它会认为await语句执行失败,会抛出异常然后跳转到catch{}错误处理。varfuna=function(){varpromiseObj_a=newPromise(function(resolve,reject){setTimeout(function(){resolve(1);},1000);});返回promiseObj_a;}varfunb=function(){varpromiseObj_b=newPromise(function(resolve,reject){setTimeout(function(){resolve(2);},5000)});返回promiseObj_b;}varfunc=function(){varpromiseObj_c=新的承诺(函数(解决,拒绝){setTimeout(函数(){拒绝(3);},8000);});返回promiseObj_c;}异步函数testAsync(){try{vara=awaitfuna();控制台.log(a,'resolve');}catch(erra){console.log(erra,'reject');}try{varb=awaitfunb();console.log(b,'解决');}catch(errb){console.log(errb,'reject');}try{varc=awaitfunc();console.log(c,'解决');}catch(errc){console.log(errc,'reject');}}testAsync();//输出是//1resolve//2resolve//3reject我们可以看到async/await结合promise带来了很大的好处。一、异步函数的执行顺序和同步一样一目了然,简单明了。其次,任何异步函数的执行都有完善的try/catch机制,错误处理非常非常容易。综上,各种方案需要结合相应的业务场景使用