说async之前,先简单提一下promise。首先纠正一下很多人的常见误解-->'promise是异步的',看代码:console.log(1);letp1=newPromise(r=>{console.log(2);r()});p1.then(_=>console.log(3));console.log(4);letp2=newPromise(r=>{console.log(5);r()});p2.then(_=>console.log(6));console.log(7);//print1245736从打印结果我们可以得出promise是同步的,那我就说promise是同步的,然后就是异步的!不是,我简单说明一下原因:先说结果:thenofpromise也是同步的。出现这个输出的原因是newpromise(fn)fn的r()函数是异步的,会挂起线程。当执行到then时,then中的代码块会立即开始执行(注意我说的是开始执行),只是将成功的回调函数放在resovledCallbacks中,但即使状态变为fulfiled,fn中的代码执行上面执行then(fn)会异步执行,console不会立即执行,因为then的内部实现根据promisA规范,delayer内部还有执行aaa的settimeout,所以then方法是肯定的是一个同步函数,但实际上它一直表现的是异步的,因为两个settimeout都保证了它是异步执行成功或失败的回调函数。具体来说,在r()内部设置了一个延迟执行回调,延迟了setTimeout的最小值,也就是说r是异步的,再看console.log(1);让p1=newPromise(r=>{console.log(2);r()});p1.then(console.log(3));console.log(4);让p2=newPromise(r=>{console.log(5);r()});p2.then(console.log(6));console.log(7);//打印1234567明白了吗?它解决的问题可以概括为:回调地狱,代码难以维护,往往第一个函数的输出是第二个函数的输入。这种现象promise可以支持多个并发请求。Promise可以通过在并发请求中获取数据来解决异步问题,但是不能说promise是异步promise就不再介绍了。如果有时间,可以深入研究一下。毕竟,promise真的能彻底解决回调地狱吗?先说一个场景,有四个函数,需要按顺序执行,就是需要等到前面的promise满了才能运行,后面的promise需要使用前面promise返回的值,例如functionf1(){returnnewPromise(resolve=>{setTimeout(_=>resolve('f1'),500)})}functionf2(params){returnnewPromise(resolve=>{console.log(params);setTimeout(_=>resolve(params+'f2'),500)})}functionf3(params){returnnewPromise(resolve=>{console.log(params);setTimeout(_=>resolve(params+'f3'),500)})}functionf4(params){returnnewPromise(resolve=>{console.log(params);setTimeout(_=>resolve(params+'f4'),500)})}我们通常这样写f1().then(res=>{returnf2(res)}).then(res=>{returnf3(res)}).then(res=>{returnf4(res)});或简化f1().then(f2).then(f3).then(f4);虽然看起来漂亮不少,但还是存在一些问题。比如你不使用第一种方法写而使用第二种方法,那么你就可以知道它的可读性很差。让我们看看f1().then(f2).then(f3).then(f4);这段代码其实根本看不出f1、f2、f3、f4之间的关系,更看不出f2、f3、f4都是用上一层的输出作为输入。我认为理想的表达方式应该是f1();f2();f3();f4();但是,如果是这样的话,我们就不能保证我们的函数是按顺序执行的,更不用说输入输出的连接了这样,我们的async就发挥作用了所以,你可以这样写void(asyncfunction(){letr1=awaitf1()letr2=awaitf2(r1)letr3=awaitf3(r2)awaitf4(r3)})();怎么样,是不是简单明了,简单介绍一下:ES7提出的async函数,终于给了JavaScript异步操作的终极解决方案。没有更多的回调地狱。异步函数是生成器函数的语法糖。使用关键字async表示,在函数内部使用await表示异步。与Generator相比,Async功能的改进在于以下四点(这四段是我在别处找到的,总结的也很好):内置执行器。Generator函数的执行必须依赖执行器,而Aysnc函数有自己的执行器,调用方式与普通函数调用相同,语义更好。async和await比*和yield更具语义和更广泛的适用性。co模块同意yield命令只能跟在Thunk函数或Promise对象之后。async函数的await命令后面可以跟一个Promise或一个原始类型的值(Number、string、boolean,但这相当于一个同步操作)。返回值是一个Promise。async函数的返回值是一个Promise对象,比Generator函数返回的Iterator对象更方便。可以直接使用then()方法调用(co模块实际上是Generator和Promise的组合,自动执行Generator)。async函数还有一个返回值,是一个Promise对象,所以我们可以使用.thenasyncfunctionf(){return1}console.log(f())//Promise{1}但是要注意,如果遇到await在函数执行过程中,它会先返回,我们再看asyncfunctionf(){await2;返回1}console.log(f());//Promise{}虽然在我的代码中是reutnr1,但是可以看到结果是一个处于pending状态的Promise对象,其中async函数内部return语句返回的值会成为回调函数的参数then方法的asyncfunctionf(){await2;return1}f().then(res=>{console.log(res)//1})值得注意的是,我一直在强调函数执行。我想表达的是,await虽然是等待执行的意思,但是对外是没有副作用的。异步函数f(){等待2;安慰。log('a')return1}f()console.log('b')//ba从打印结果可以看出,程序执行过程中虽然遇到了await,但是并没有阻塞外部代码的执行,所以仍然没有改变Javascript的异步特性,但至少我们可以在异步函数中很好地控制我们的进程。让我们看一道题,看看这种语法糖的威力。目标:发送30个ajax请求,要求30个请求是串行的(即发送请求时必须等待前面的requestres)。如果我们使用常规的promisepromise.then方法,这道题将很难实现。我们先模拟一个ajax请求,假设每个ajax的timeresponse是400ms->functionajax(n){returnnewPromise((rs,rj)=>{setTimeout(()=>{console.log(n);rs()},400)})}Promise实现:letn=50lettask=ajax(n);functionrun(){task.then(_=>{--n&&(task=ajax(n))&&跑跑();生成器实现letnum=50;function*Ge(){while(true){yieldajax(num)}}letre=Ge()functionrun(){letresult=re.next().valueresult.then(_=>{num--&&run()})}run()asyncimplementationletn=50asyncfunctionrun(){while(n--)awaitajax(n)}run()经过比较,是一目了然。刚才说async其实就是Generator的语法糖。有些人会好奇async是如何实现的。要想看懂,还是要学习生成器。async毕竟只是generator的语法糖,跳过它直接学习async当然会漏掉很多。async等于Generator+automaticexecutor。回到前面的例子void(asyncfunction(){letr1=awaitf1()letr2=awaitf2(r1)letr3=awaitf3(r2)awaitf4(r3)})();我们说async,如果遇到await,会等待后面的Promise返回结果(同步除外),所以上面代码中的执行顺序是f1->f2->f3,那么这就带来了一个问题,我们有toletf1,2,3如何同时并发执行?我们知道Promise是同步的。当我们newPromise(...)的时候,它其实已经开始执行了,但是返回结果是一个带state的P,那么如果我们想让f1,2,3并行的话,有一个办法void(asyncfunction(){letr1=newPromise(...)letr2=newPromise(...)letr3=newPromise(...)awaitr1awaitr2awaitr3})();这相当于newPromise中的代码块是同时执行的,至于状态从pending变为full的时间长短视业务需求和场合而定,另外一种方法可能void会更直观(asyncfunction(){letre=awaitPromise.all([p1,p2,p3])})();其中re是一个数组,其值分别对应p1、2、3;改成race当然也可以使用Promise.race([p1,p2,p3])。如果请求比较多,我们也可以使用map和foreach并行执行functionplist(n)。{returnnewPromise(resolve=>{console.log('start:'+n)setTimeout(_=>{resolve(n)},2000)})}letc=[...newArray(100).键()]让pros=c.map(asyncn=>{returnawaitplist(n)})for(letpofpros){p.then(res=>console.log('end:'+res))}mapwithforEach它们都是并行执行promise数组,但是for-infor-of-for都是序列化的。知道了这两点,我们就可以高效的处理很多异步请求了。最后简单说一下async的错误处理方式。我们都知道promise中的异常或拒绝是无法通过trycatch捕获的,例如try{Promise.reject('annormalerror')}catch(e){console.log('errorcomming')console.log(e)}这个错误不能被trycatch捕获,会报UnhandledPromiseRejectionWarning的错误描述,再比如functionfn(){try{newPromise(resolve=>{JSON.parse(a)resolve()})}catch(e){console.log('errorcomming')console.log(e)}}fn()这里直接抛出ReferenceError异常,我们把它放到asyncasyncfunctionfn(){try{awaitnewPromise(resolve=>{JSON.parse(a)resolve()})}catch(e){console.log('errorcomming')console.log(e)}}fn()是惊人的。异常被捕获。事实上,我不确定这的真正原因。我觉得关键是await做了什么,对执行环境有什么影响。先说说我的观点吧,因为promise是非阻塞的,也就是说,对于promise外部的try和catch,内部的promise是异步执行的,trycatchch无法捕获异步错误,而await的意思是等待promise的执行,等待promise的执行结果,并暂停当前async执行环境的代码执行,也就是说在async下,我们甚至可以认为await是同步的,是阻塞的!所以我们可以认为这个错误是一样的stepthrowing是由(awaitnewPromise(...))抛出的,所以会被抓到。但是,我不建议使用这种方法来异步捕获异常。一是代码结构看起来比较混乱,二是如果try/catch的catch部分出现异常,应该怎么处理?所以我建议使用async().catch来处理,因为async不管有没有返回值都会返回一个promise对象asyncfunctionfn(){}console.log(fn().then)//[Function...]而async也可以使用return来返回一个promiseasyncfunctionfn(){//returnawaitPromise.resolve(1)//returnPromise.resolve(1)}这里简单介绍一下async,以后再讨论下一篇我们来说说Generator,让大家知道为什么我们很少使用Generator或者为什么async是Generator的语法糖