在前面的两篇博客中,我们介绍了ES6 的
promise
和generator
,它们都是用来解决 js中的异步操作问题的。本篇博客的主角,async
函数,同样是用于解决异步回调问题,它甚至被称为异步操作的终极解决方案。使用它,我们可以像处理简单的同步操作一样来处理一些异步操作,彻底告别回调函数方式的回调地狱问题,promise
方法绵长的then
调用问题,generator
函数需要引入 co 库等这些异步处理方案中恼人的不完美之处。准确的来说,
async
函数其实是 ES7 中的新语言特性,但babel已经完全提供了它的转码,所以在可以使用 ES6的地方,我们都可以大胆的使用async
函数。为了图个方便,就将此篇博客无伤大雅的放在 ES6 系列中,所以特此说明。
好了,下面就来具体介绍以下我们今天的主角吧。
基本概念
async
函数,其实是将promise
和generator
函数结合在一起,从而将异步处理步骤大大简化的一种函数。
首先,我们来复习一下系列博客的前两篇中介绍的generator
和promise
。
generator
函数,通过为函数定义添加 * 号来声明,通过yield
关键字,得到异步结果,并在我们使用next
执行器时,将异步结果传递给我们。它的缺点在于,我们必须在异步处理完成时,调用next
执行器来进行结果传递,这意味着,我们必须对一些异步 API进行二次包装,增加了异步处理的复杂性。虽然 co库为我们提供了类似自动执行器的功能,但要求异步操作必须返回一个thunk
函数或promise
,在实际使用中仍然是比较麻烦的。具体见ES6之generator
promise
,将回调函数规范化,通过 resolve
传递异步操作结果,通过reject
传递异步操作异常,通过then
的链式调用,来将异步操作序列化。但在一些复杂的异步操作时,new Promise(function(resolve,reject){...})
,这种冗长的写法,以及大串的then
调用,也会使代码混乱和难以维护。具体见ES6之promise
而async
函数,作为generator
函数的语法糖,将generator
的 yield
机制,通过包装为promise
实现自动执行,从而避免了我们手动使用next
执行器或者使用一些类似co库这种自动执行器的麻烦。
Async
首先来看一下async
函数的基本语法:
1 | //通过在函数声明前加上 async,来声明函数是一个async函数。 |
简单的在函数前加上async
关键字,就声明了函数 foo
为一个异步的 async
函数。我们可以像调用一个普通函数一样调用它。
但特别之处在于,这个函数并不会像普通函数一样简单的返回我们return
的值,它总是将我们在函数中return
的值转化为一个promise
对象返回。
1 | console.log(foo()) |
通过上面的代码,我们可以清楚的看出async
函数总是返回一个promise
对象。还记得我们在promise
中提到的resolve
实例方法吗?async
就是使用它,来将我们在函数内部声明的返回值包装为promise
对象的。
当然,如果我们不在async
函数中显式返回任何值,那么它就会返回 Promise.resove(undefined)
。
Await
async
函数当然不仅仅只是返回一个promise
对象这么简单,通过Await
关键字,才能真正体现出async
无阻塞,以同步方式来书写异步代码的魔力所在。
await
关键字,用在async
函数中,从名字也可以看出,它用于等待一个异步操作的完成并得到异步操作的结果。类似yield
关键字只能用在generator
函数中,await
也只能用在async
中。
await
关键字,会等待跟随在它后面的表达式完成,取得表达式的值。如果它后面跟随的表达式返回一个promise
对象,它就会执行这个promise
对象的then
方法,得到promise
的resolved
值。
通过代码来看一下它的具体作用:
1 | //我们定义一个返回promise对象的异步函数 |
从上面的代码我们可以看出,await
关键字,将其后跟随的表达式进行解析,如果是promise
对象,就会等待promise
对象转换为fullfilled
状态,得到其resolve
出来的值。
await
简洁直接的解决了我们调用异步操作并得到异步操作结果这个过程的麻烦,在遇到异步操作的处理时,我们只需要简单的在其前面加上await
,它会去执行这个异步操作,等待着得到异步的结果,从而让我们的代码逻辑清晰,再也不需要那一长条的then
链了。
另外,await
后面如果跟随的是一个同步操作,它也会直接返回这个同步操作的结果值,跟不加await
是一样的。这也解决了我们使用generator
时,yield
必须返回一个thunk
函数或promise
对象才能被co库自动执行的问题。
异常处理
async/await
,已经算是比较完美的异步处理方案了。但它仍然有美中不足的地方,那就是异常处理。
我们知道,promise
会有两种状态,fullfilled
和rejected
,当异步操作发生错误时,promise
会进入rejected
状态,传递出错误。但是在async
函数中,await
只会去自动获取其得到的promise
的resolve
值,并不关心reject
值。这也就意味着,我们必须自己去处理异步操作返回promise
的rejected
状态。
首先,第一种方法,直接了当,我们在await
后面的promise
对象上直接处理reject
。代码形如下:
1 | async function foo() { |
但是上面这种方法,在async
包装了很多异步操作时,有很多await
时,需要为每个await
后面的异步操作都加上catch
,显得臃肿和累赘,非常不优雅。
第二种方法,就是使用try/catch
,将多个异步操作的await
都放在try
块中,并在catch
块中统一处理。
1 | async function foo() { |
这种方法,算是在实际应用中使用的较多的一种方法,它能够方便的同时处理同步和异步错误。但是在异步操作逻辑比较复杂时,也并不是那么的优雅,会一定程度增加代码的不可读性和复杂度。
总结
关于await/async
函数的介绍,就到这里。其实如果仔细的了解了generator
函数和promise
,async
函数是非常容易理解的,也可以很直观的体会到它相比于其他两者的简洁优雅。作为 JavaScript 近年来推出的最革命性的特性之一,相信在后面的前端领域发展中,它也会发挥越来越重要的作用。
好啦,这篇博客就到这里。谢谢阅读,鞠躬退场^O^。