异步编程async/await的终极解决方案:用同步的方式写异步代码;})但是这个回调函数有一个很大的缺陷,就是会写回调地狱(Callbackhell)。比如多个回调有依赖关系,可以这样写:ajax(url,(res)=>{console.log(res);//...处理代码ajax(url2,(res2)=>{console.log(res2);//...处理代码ajax(url3,(res3)=>{console.log(res3);//...处理代码})})})这是回调地狱:嵌入式函数存在Coupling,一招牵一发而动全身,改一个又会影响其他地方的内嵌函数,出错怎么处理?这是一个难题。早期回调函数的优缺点:优点:解决了同步阻塞的问题(只要一个任务耗时长,后面的任务就必须排队,会延迟整个程序的执行)缺点:回调地狱;不能用trycatch来捕捉错误;can'treturntransitionschemeGeneratorES6新引入了Generator函数(generatorfunction),可以通过yield关键字暂停函数的执行流程,提供了改变执行流程的可能,从而提供了异步编程的解决方案。最大的特点是可以控制函数的执行。Generator有两部分区别于普通函数:一是在函数后面,函数名前有一个*,用来表示该函数是一个Generator函数。函数内部有一个yield表达式,用来定义函数内部的状态。Generator函数的具体使用方法是:在Generator函数内部执行一段代码,如果遇到yield关键字,JS引擎会将关键字后面的内容返回给外部,并暂停该函数的执行。外部函数可以通过next方法恢复函数执行。function*fn(){console.log("one");yield'1';console.log("two");yield'2';console.log("three");return'3';}调用Generator函数和调用普通函数一样,只是在函数名后面加上(),但是Generator函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以需要调用迭代器对象Iteratornext方法,指针将从函数头或上次停止的地方开始执行。如下:next方法:一般情况下,当next方法不传入参数时,yield表达式的返回值是undefined。next传入参数时,该参数将作为上一步yield的返回值。Generator生成器也是用同步的方式写异步代码,这样也可以解决回调地狱的问题,但是比较难理解。希望下面的例子可以帮助大家理解Generator生成器:function*sum(a){console.log('a:',a);让b=产量1;console.log('b:',b);让c=产量2;console.log('c:',c);让总和=a+b+c;console.log('sum:',sum)returnsum;}next没有传递参数时,yield返回undefined第一次执行next时,传入的参数会被忽略,函数暂停在yield1处,所以return1第二次执行next时,没有传参数,然后yield1返回undefined,所以b的值为undefined。同样,c的值第三次未定义。当next作为参数传入时,该参数将作为上面单步yield的返回值,如下图所示:第一次执行next时,参数(20)会被忽略,函数在yield1处暂停,所以返回1。第二次执行next时,传入参数30作为yield1返回的值,所以b=yield1,b的值为30next时第二次执行时,参数40作为yield2的返回值传入,所以c=yield2,c的值为40coroutine我们知道,async/await是一个自动执行的Generator函数。上面介绍了Generator函数,那么有必要介绍一下V8引擎是如何实现一个函数的暂停和恢复的。要理解函数为什么可以挂起和恢复,首先要理解协程的概念。我们都知道进程和线程,那么什么是协程呢?协程比线程更轻量级。协程可以看作是运行在线程上的任务。一个线程上可以有多个协程,但一个线程上同时只能执行一个协程。比如当前正在执行协程A,需要启动协程B,则A协程需要将主线程的控制权交给B协程,体现在A协程暂停执行,B协程恢复执行;同样,A协程也可以从B协程启动。通常,如果协程B是从协程A启动的,我们称协程A为协程B的父协程。正如一个进程可以有多个线程一样,一个线程也可以有多个协程。最重要的是,协程不由操作系统内核管理,而完全由程序控制(即在用户态执行)。这样做的好处是性能有了很大的提升,不会像线程切换那样消耗资源。结合代码可以理解:function*genDemo(){console.log("开始执行第一段")yield'generator2'console.log("开始执行第二段")yield'generator2'console.log("开始执行第三段")yield'generator2'console.log("执行结束")return'generator2'}console.log('main0')letgen=genDemo()console.log(gen.next().value)console.log('main1')console.log(gen.next().value)console.log('main2')console.log(gen.next().value)console.log('main3')console.log(gen.next().value)console.log('main4')执行过程如下图所示,可以重点关注协程之间的切换:从图中可以看出程序的四点规则:通过调用生成器函数genDemo创建一个协程gen。创建后,gen协程不会立即执行。要让gen协程执行,需要调用gen.next。协程执行时,可以使用yield关键字暂停gen协程的执行,将主要信息返回给父协程。如果协程在执行过程中遇到return关键字,JS引擎会结束当前协程,将return后的内容返回给父协程。协程切换:gen协程和父协程在主线程上交互执行,不是并发执行。他们之前的切换是通过yield和gen.next的配合完成的。当在gen协程中调用yield方法时,JS引擎会保存gen协程当前的调用栈信息,并恢复父协程的调用栈信息。同样,在父协程中执行gen.next时,JS引擎会保存父协程的调用栈信息,并恢复gen协程的调用栈信息。其实在JS中,Generator生成器就是协程的一种实现。async/await的最终解决方案是使用Promise来解决回调地狱的问题,但是这个方法充满了Promise的then()方法。如果处理流程比较复杂,那么整个代码就会充满then,语义也不好。显然,代码并不能很好地表示执行流程。为此,ES7引入了async/await,这是对JavaScript异步编程的重大改进,提供了使用同步代码异步访问资源而不阻塞主线程的能力,使代码逻辑更加清晰。其实async/await技术背后的秘密就是Promise和Generator的应用,更底层就是microtasks和coroutines的应用。要理解async和await是如何工作的,我们必须分别分析async和await。什么是异步?根据MDN定义,async是一个异步执行并隐式返回Promise作为结果的函数。关注两个词:异步执行和隐式返回一个Promise。先来看看如何隐式返回Promise,参考如下代码:asyncfunctionasync1(){return'秀儿';}console.log(async1());//Promise{
