ECMAScript6(简称ES6)将JavaScript异步编程带入了一个全新的阶段。这篇文章的主题是介绍一种更强大更完整的ES6异步编程方法。首先我们来回顾一下javascript异步的发展历程。ES6之前:回调函数(callback):常用于nodejsexpress,常用于ajax。ES6:promise对象:nodejs最先有了bluebirdpromise的原型,在axios中常用。Generator函数:大量使用nodejskoa框架。ES7:async/await语法:目前最常用的异步语法,nodejskoa2完全使用了这种语法。什么是异步所谓“异步”简单来说就是把一个任务分成两段,先执行第一段,然后再执行其他任务,准备就绪后,再执行第二段。比如有一个任务读取一个文件进行处理,异步执行流程如下。在上面的异步图中,任务的第一部分是向操作系统发送读取文件的请求。然后程序执行其他任务,等待操作系统返回文件,然后继续执行任务的第二部分(处理文件)。这种不连续的执行称为异步。相应地,连续执行称为同步。同步上图是同步的执行方式。由于是连续执行,不能插入其他任务,所以程序只能等待操作系统从硬盘读取文件。回调函数回调JavaScript异步编程语言的实现就是回调函数。所谓回调函数就是把任务的第二段单独写在一个函数里,当任务重新执行的时候直接调用这个函数。它的英文名callback,直译为“召回”。回调从字面上也很容易理解,就是先处理主体函数,再处理回调函数。比如方便大家理解。上面的例子很容易理解,先执行主函数A,打印结果:我是主题函数;然后执行回调函数callback,也就是B,打印结果:我是回调函数。promise对象promise对象用于表示异步操作的最终完成(或最终失败)及其结果。简单地说,它处理一个异步请求。我们经常会做出一些断言,我赢了你嫁给我,我输了我娶你等等。这就是promise的中文意思:断言,一成功,一失败。比如方便大家理解:promise构造函数的参数是一个函数,我们称之为处理器函数。处理器函数接收两个函数reslove和reject作为其参数。当异步操作执行成功后,执行reslove函数。当异步操作出现异常时,执行reject函数。通过resolve传入的值可以在then方法中获取,通过reject传入的值可以在chatch方法中获取。因为then和catch都返回相同的promise对象,所以它们可以被链接起来。Promise的写法只是对回调函数的一种改进。使用then方法后,可以更清晰的看出异步任务的两阶段执行。除此之外,再无新意。Promise最大的问题是代码冗余。原始任务由Promise包装。不管是什么操作,一看就是一堆then,原本的语义变得很不清晰。那么,有没有更好的写法呢?协程这个传统的编程语言早就有了针对异步编程的解决方案(其实就是多任务的解决方案)。其中之一称为“协程”,意思是多个线程相互协作完成异步任务。协程有点像函数,也有点像线程。其运行过程大致如下。第一步,协程A开始执行。第二步,协程A执行到一半,进入暂停,执行权转移给协程B。第三步,(一段时间后)协程B归还执行权。第四步,协程A恢复执行。上述流程的协程A是一个异步任务,因为它分为两次(或多次)执行。比如读取文件的协程是这样写的。functionasnycJob(){//...othercodevarf=yieldreadFile(fileA);//...othercode}上面代码中的函数asyncJob是一个协程,它的秘密就在于yield命令。意思是执行到这里,执行权交给其他协程。换句话说,yield命令是两个异步阶段的分界线。当协程遇到yield命令时,会暂停,等待执行权归还,然后从暂停的地方继续执行。它最大的优点是代码写的很像同步操作。如果去掉yield命令,就完全一样了。Generator函数Generator函数是ES6中coroutine的实现,最大的特点是可以交出函数的执行权(即挂起执行)。function*gen(x){vary=yieldx+2;returny;}上面的代码是一个Generator函数。它与普通函数不同,可以暂停执行,所以在函数名前加星号以示区别。整个Generator函数就是一个封装好的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方用yield语句标示。Generator函数的执行方法如下。varg=gen(1);g.next()//{value:3,done:false}g.next()//{value:undefined,done:true}上面代码中调用Generator函数会返回a内部指针(即遍历器)g.这是Generator函数与普通函数不同的另一个地方,就是执行它不会返回结果,而是一个指针对象。调用指针g的next方法会将内部指针(即异步任务执行的第一段)移动到指向第一个遇到的yield语句。上面的例子一直执行到x+2,换句话说,next方法做的就是分阶段执行Generator函数。每次调用next方法时,都会返回一个对象表示当前阶段的信息(value属性和done属性)。value属性是yield语句后的表达式的值,表示当前阶段的值;done属性是一个布尔值,表示Generator函数是否已经执行完毕,即是否有下一阶段。Generator函数的用法让我们看看如何使用Generator函数来执行一个真正的异步任务。varfetch=require('node-fetch');function*gen(){varurl='https://api.github.com/users/github';varresult=yieldfetch(url);console.log(result.bio);}上面代码中,Generator函数封装了一个异步操作,先读取一个远程接口,然后从JSON格式的数据中解析出信息。如前所述,除了添加了yield命令外,这段代码非常像同步操作。执行这段代码的方法如下。varg=gen();varresult=g.next();result.value.then(函数(数据){returndata.json();}).then(函数(数据){g.next(数据);});上面代码中,首先执行Generator函数获取遍历器对象,然后使用next方法(第二行)执行异步任务的第一阶段。由于Fetch模块返回的是一个Promise对象,所以使用then方法调用下一个next方法。可以看出,Generator函数虽然把异步操作表达的很简洁,但是流程管理不方便(即什么时候执行第一阶段,什么时候执行第二阶段)。async-awaitasync函数返回一个promise对象。如果在async函数中直接返回一个值,async会通过Promise.resolve封装成一个Promise对象。我们可以通过调用promise对象的then方法来获取这个字面量。那么异步函数没有返回值怎么办?await会暂停当前async的执行,await会阻塞代码的执行。代码可以继续执行,直到处理完await之后的表达式。await之后的表达式可以是Promise对象或任何要等待的值。如果await等待一个Promise对象,await就会很忙。它会阻塞下面的代码,等待Promise对象解析,然后获取解析后的值作为await表达式的结果。看到上面的blocking这个词,不要惊慌,async/await只是一个语法糖,代码执行和多次回调嵌套调用没什么区别。本质上不是同步代码,只是让你在思考代码逻辑的时候同步思考,避免回调地狱。总之——async/await就是用同步的思维写异步代码,所以async/await不会影响node的并发数,大家可以大胆的应用到项目中!如果它不是Promise对象,则await表达式的结果就是它等待的结果。例如,为了便于理解:
