购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.12 异步编程

ES6为异步操作带来了3种新的解决方案,分别是Promise、Generator、async/await。规避了异步编程中回调地狱问题,解决了异步编程中异常难以处理、编程代码复杂等问题。

下面分别介绍这3种方案的详细用法。

2.12.1 Promise

Promise是异步编程的一种解决方案,比传统的以回调函数和事件方式处理异步操作更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise是一个对象,用来表述一个异步任务执行之后是成功还是失败。

1.Promise的语法格式

Promise的语法格式,如代码示例2-139所示。

代码示例2-139

在上面的代码中,new Promise(fn)返回一个Promise对象,在fn函数中定义异步操作,resolve和reject分别是两个函数,如果该异步处理的结果正常,则调用resolve(处理结果值),返回处理的结果。如果处理的结果错误,则调用reject(Error对象),返回错误信息。

接下来通过一个异步请求的例子,进一步了解Promise的用法,如代码示例2-140所示。

代码示例2-140

2.Promise的状态

Promise的实例对象有3种状态:pending、fulfilled、rejected。Promise对象根据状态来确定执行哪种方法。Promise在实例化的时候默认状态为pending。

(1)pending:等待状态,如正在进行网络请求时,或者定时器没有到时间,此时Promise的状态就是pending状态。

(2)fulfilled:完成状态,当主动回调了resolve时,就处于该状态,并且会回调.then()方法。

(3)rejected:拒绝状态,当主动回调了reject时,就处于该状态,并且会回调.catch()方法。

这里需要注意,Promise的状态无论修改为哪种状态,之后都是不可改变的。

3.Promise链式调用

Promise的链式调用的方法尽可能地保证异步任务的扁平化。链式调用如代码示例2-141所示。

代码示例2-141

在上面的代码中,promise对象的then()方法返回了全新的promise对象。可以再继续调用then()方法,如果return的不是promise对象,而是一个值,则这个值会作为resolve的值传递,如果没有值,则默认为undefined。

(1)后面的.then()方法就是在为上一个.then()返回的Promise注册回调。

(2)前面.then()方法中回调函数的返回值会作为后面.then()方法回调的参数。

(3)如果回调中返回的是Promise,则后面.then()方法的回调会等待它的结束。

4.Promise异常处理

Promise对异常处理做了很好的设计,让错误处理变得轻松和方便。

错误机制的API就是reject()方法和.catch()方法,前者负责发起一个错误并往下游传递,后者负责捕获从上游传递下来的错误。可以说,它们共同担当了错误机制的建设,如代码示例2-142所示。

代码示例2-142

在上面的代码中,如果出现错误,则会依次经过.then()和.catch(),其中错误经过.then(resolve,reject)时,可能出现3种情况:

(1).then()只提供了reject方法处理回调逻辑,没有提供reject方法处理错误。

(2).then()提供reject方法处理了错误。

(3).then()中的代码执行时本身又出了新的错误。

总体来讲:只要错误被提供的reject方法处理了,下游将不会出现这个错误;只要存在错误,并且不曾被方法处理,最终都会被.catch()捕获。

catch是promise原型链上的方法,用来捕获reject抛出的异常,进行统一的错误处理,使用.catch()方法更为常见,因为更加符合链式调用。

5.批量异步操作

Promise提供了两个批量异常操作的API:promise.all和promise.race。

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject方法处理的失败状态的值,如代码示例2-143所示。

代码示例2-143

Promise.race是赛跑的意思,意思就是Promise.race([p1,p2,p3])里面哪个结果获得得快,就返回哪个结果,不管结果本身是成功状态还是失败状态,如代码示例2-144所示。

代码示例2-144

6.Promise的优缺点

Promise有以下两个优点:

(1)Promise将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

(2)Promise对象提供统一的接口,使控制异步操作更加容易。

Promise的缺点如下:

(1)无法取消Promise,一旦新建它就会立即执行,无法中途取消。

(2)如果不设置回调函数,则Promise内部抛出的错误不会反映到外部。

(3)当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

2.12.2 Generator

为了解决Promise的问题,ES6中提供了另外一种异步编程解决方案(Generator)。Generator()函数的优点是可以随心所欲地交出和恢复函数的执行权,yield用于交出执行权,next()用于恢复执行权。

Generator()函数返回一个Iterator接口的遍历器对象,用来操作内部指针。每次调用遍历器对象的next()方法时,会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

1.yield关键字

yield关键字使生成器函数暂停执行,并返回跟在它后面的表达式的当前值。可以把它想成是return关键字的一个基于生成器的版本,但其并非退出函数体,而是切出当前函数的运行时,与此同时可以将一个值带到主线程中。yield语句是暂停执行的标记,而next()方法可以恢复执行,如代码示例2-145所示。

代码示例2-145

输出结果如下:

上面代码的说明如下:

(1)当遇到yield语句时暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值作为返回的对象的value属性值。

(2)下一次调用next()方法时,再继续往下执行,直到遇到下一个yield语句。

(3)如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。

注意: yield语句后面的表达式,只有当调用next()方法、内部指针指向该语句时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

在代码示例2-146中,yield后面的表达式123+456不会立即求值,只会在next()方法将指针移到下一句时才会求值,如代码示例2-147所示。Generator()函数也可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。

代码示例2-146

代码示例2-147

2.next()方法的参数

注意: yield句本身没有返回值(返回undefined)。next()方法可以带一个参数,该参数会被当作上一个yield语句的返回值,如代码示例2-148所示。

代码示例2-148

next()方法不带参数,导致y的值等于2∗undefined(NaN),除以3以后还是NaN;next()方法提供参数,第一次调用b的next()方法时,返回x+1的值6;第二次调用next()方法,将上一次yield语句的值设为12,因此y等于24,返回y/3的值8。

3.for…of循环

for…of循环可以自动遍历Generator()函数生成的Iterator对象,并且此时不再需要调用next()方法,如代码示例2-149所示。

代码示例2-149

利用Generator()函数和for...of循环实现斐波那契数列,如代码示例2-150所示。

代码示例2-150

4.yield∗

yield∗一个可迭代对象,相当于把这个可迭代对象的所有迭代值分次yield出去。表达式本身的值就是当前可迭代对象迭代完毕(当done为true时)时的返回值,如代码示例2-151所示。

代码示例2-151

判断是否为Generator()函数,如代码示例2-152所示。

代码示例2-152

2.12.3 async/await

async函数是ES7提出的一种新的异步解决方案,它与Generator()函数并无大的不同。语法上只是把Generator()函数里的∗换成了async,将yield换成了await,它是目前为止最佳的异步解决方案。

与Generator()函数比较起来,async/await具有以下优点:

(1)内置执行器。这表示它不需要不停地使用next来使程序继续向下进行。

(2)更好的语义。async代表异步,await代表等待。

(3)更广的适用性。await命令后面可以跟Promise对象,也可以是原始类型的值。

(4)返回的是Promise。

async()函数返回一个Promise对象,可以使用then()方法添加回调函数。当函数执行时,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句,如代码示例2-153所示。

代码示例2-153

await关键字必须出现在async()函数中,一般来讲await命令后面是一个Promise对象,返回该对象的结果,如果不是Promise对象就直接返回对应的值,如代码示例2-154所示。

代码示例2-154

等同于代码示例2-155中的代码。

代码示例2-155

任何一个await语句后面的Promise对象变成reject状态,整个async()函数都会中断执行。如果希望前一个异步操作失败,则不要中断后面的异步操作,这时可以将await放在try...catch结构里,如代码示例2-156所示。

代码示例2-156 FTO7lhCMHCPi5ON9Nx8ydo9BLJTh6+ojKWd9PCmeg5MH4GdRKV1Xi1puzH7O2iww

点击中间区域
呼出菜单
上一章
目录
下一章
×