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

为什么async-await不只是语法糖

时间:2023-03-27 12:49:10 JavaScript

微信搜索【伟大的走向世界】,第一时间与大家分享前端行业动态、学习路径等。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。开篇,async/await不仅仅是Promise之上的语法糖,因为async/await确实提供了实实在在的好处。async/await使异步代码同步,使代码更具表现力和可读性。async/await统一了异步编程体验;并提供更好的错误堆栈跟踪。关于JS中异步编程的一点历史异步编程在JavaScript中很常见。每当我们需要进行网络服务调用、文件访问或数据库操作时,尽管语言是单线程的,异步性是我们防止用户界面阻塞的方式。在ES6之前,回调是猿类处理异步编程的方式。我们可以表达时间依赖性(即异步操作的执行顺序)的唯一方法是将一个回调嵌套在另一个回调中,这会导致所谓的回调地狱。Es6中引入了Promise,它是用于异步操作的一流对象,我们可以轻松地传递、组合、聚合和应用转换。通过then方法链可以清楚地表达时间依赖性。有了Promises这个强大的伙伴,听起来异步编程在JS中是一个解决的问题,对吧?好吧,还没有,因为有时Promises太低级而无用。有时Promises太低级而无用尽管有Promises,但在JS中仍然需要更高级别的语言结构来进行异步编程。让我们看一个例子,假设我们需要一个函数来以特定的时间间隔轮询API。当达到最大重试次数时,它解析为null。这是Promise的解决方案:letcount=0;functionapiCall(){returnnewPromise((resolve)=>//a在第6次重试时被解析为“value”。计数++===5?resolve('value'):resolve(null));}functionsleep(interval){returnnewPromise((resolve)=>setTimeout(resolve,interval));}functionpoll(retry,interval){returnnewPromise((resolve)=>{//为简洁起见跳过错误处理if(retry===0)resolve(null);apiCall().then((val)=>{if(val!==null)resolve(val);else{sleep(interval).then(()=>{resolve(poll(retry-1,interval));});}});});}poll(6,1000).then(console.log);//'value'这个解决方案的直观性和可读性取决于一个人对Promises的熟悉程度,以及Promise.resolve如何“平铺”Promise和递归。对我来说,这不是编写此类函数的最易读的方式。使用async/await我们用async/await语法重写上面的解决方案:asyncfunctionpoll(retry,interval){while(retry>=0){constvalue=awaitapiCall().catch((e)=>{});如果(值!==null)返回值;等待睡眠(间隔);重试-;}returnnull;}我认为大多数人会发现上述解决方案更具可读性,因为我们可以使用所有Normal语言结构,例如循环、用于异步操作的try-catch等。这可能是async/await最大的卖点——允许我们以同步的方式编写异步代码。另一方面,这可能是对async/await最常见的反对意见的来源,稍后会详细介绍。顺便说一下,await甚至具有正确的运算符优先级,所以awaita+awaitb等于(awaita)+(awaitb)而不是await(a+awaitb)。async/await提供同步和异步代码的统一体验async/await的另一个好处是await自动将任何非Promise(非thenables)包装到Promises中。await的语义等同于Promise.resolve,这意味着你可以等待任何东西:functionfetchValue(){return1;}asyncfunctionfn(){constval=awaitfetchValue();控制台日志(val);//1}//上面相当于下面functionfn(){Promise.resolve(fetchValue()).then((val)=>{console.log(val);//1});}如果我们附加then方法到fetchValue返回的数字1上,出现如下错误。functionfetchValue(){return1;}functionfn(){fetchValue().then((val)=>{console.log(val);});}fn();//?未捕获的类型错误:fetchValue(...).then不是一个函数最后,从异步函数返回的任何东西都是一个Promise:Object.prototype.toString.call((asyncfunction(){})());//'[objectPromise]'async/awaitprovidesbettererrorstacktracesV8工程师Mathias写了一篇名为Asynchronousstacktraces:whyawaitbeatsPromise#then()的文章,解释了为什么引擎比async更容易捕捉和存储/await的Promise堆栈跟踪。示例:asyncfunctionfoo(){awaitbar();返回“价值”;}functionbar(){thrownewError('BEEPBEEP');}foo().catch((error)=>console.log(error.stack));//Error:BEEPBEEP//atbar(:7:9)//atfoo(:2:9)//at:10:1asyncversion正确捕获了错误堆栈跟踪。让我们再看看Promise版本。functionfoo(){returnbar().then(()=>'value');}functionbar(){returnPromise.resolve().then(()=>{thrownewError('BEEPBEEP');});}foo().catch((error)=>console.log(error.stack));//错误:BEEPBEEPat:7:11堆栈跟踪丢失。从匿名箭头函数切换到命名函数声明有一点帮助,但作用不大:functionfoo(){returnbar().then(()=>'value');}functionbar(){resolve().then(functionthisWillThrow(){thrownewError('BEEPBEEP');});}foo().catch((error)=>console.log(error.stack));//错误:BEEPBEEP//atthisWillThrow(:7:11)对async/await的常见反对意见对async/await的反对意见主要有两个。首先,当独立的异步函数调用可以和Promise.all并发处理时,如果我们也大量使用async/await,可能会导致滥用,这会导致开发者不去尝试去了解Promise背后是如何工作的,而是只是盲目地使用异步/等待。第二种情况更微妙。一些函数式编程爱好者认为async/await需要命令式编程。从FP程序员的角度来看,能够使用循环和trycatch并不是一件好事,因为这些语言结构意味着副作用并鼓励不太理想的错误处理。我对这个声明保留意见。FP程序员理所当然地关心他们程序中的确定性。他们希望对自己的代码有绝对的信心。为此,需要一个复杂的类型系统,其中包括Result等类型。但我不认为async/await本身与FP不兼容。不管怎样,对于大多数人来说,FP仍然是一种后天习得的品味,包括我(尽管我确实认为FP非常酷,而且我正在慢慢学习它)。async/await提供的正常控制流语句和trycatch错误处理对于我们在JavaScript中协调复杂的异步操作来说是无价的。这就是为什么说“async/await只是语法糖”是一种轻描淡写的说法。代码部署后可能存在的bug,无法实时获知。事后为了解决这些bug,花费了大量的时间在日志调试上。顺便推荐一个好用的bug监控工具Fundebug。作者:zhenghao译者:前端小智来源:zhenghao原文:https://www.zhenghao.io/posts...交流有梦想,有干货,微信搜索【大千世界】关注这个我还在清晨洗碗洗碗的智慧。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。,