深入理解ES7的async/await的用法

在最开始学习ES6的Promise时,曾写过一篇博文 《promise和co搭配生成器函数方式解决js代码异步流程的比较》 ,文章中对比了使用Promise和co模块搭配生成器函数解决js异步的异同。

在文章末尾,提到了ES7的async和await,只是当时只是简单的提了一下,并未做深入探讨。

在前两个月发布的Nodejs V7中,已添加了对async和await的支持,今天就来对这个东东做一下深入的探究。以更加优雅的方法写异步代码。

async/await是什么

async/await可以说是co模块和生成器函数的语法糖。用更加清晰的语义解决js异步代码。

熟悉co模块的同学应该都知道,co模块是TJ大神写的一个使用生成器函数来解决异步流程的模块,可以看做是生成器函数的执行器。而async/await则是对co模块的升级,内置生成器函数的执行器,不再依赖co模块。同时,async返回的是Promise。

从上面来看,不管是co模块还是async/await,都是将Promise作为最基础的单元,对Promise不很了解的同学可以先深入了解一下Promise。

对比Promise,co,async/await

下面我们使用一个简单的例子,来对比一下三种方式的异同,以及取舍。

我们采用mongodb的nodejs驱动,查询mongodb数据库作为例子,原因是mongodb的js驱动已经默认实现了返回Promise,而不用我们单独去包装Promise了。

使用Promise链

MongoClient.connect(url + db_name).then(db=> { return db.collection('blogs'); }).then(coll=> { return coll.find().toArray(); }).then(blogs=> { console.log(blogs.length); }).catch(err=> { console.log(err); })

Promise的then()方法可以返回另一个Promise,也可以返回一个同步的值,如果返回的是一个同步值,将会被包装成一个Promise。

上面的例子中,db.collection()将返回一个同步的值,即集合对象,但是被包装成Promise,将会透传到下一个then()方法。

上面一个例子,是使用的Promise链。

先连接数据库MongoClient.connect()返回一个Promise,然后在then()方法里获得数据库对象db,然后再获取到coll对象再返回。在下一个then()方法获得coll对象,然后进行查询,查询结果返回,逐层调用then()方法,形成一个Promise链。

在这个Promise链上,如果任何一个环节出现异常,都会被最后的catch()捕捉到。

可以说,这个使用Promise链写的代码,比层层调用回调函数更优雅,流程也更明确。先获得数据库对象,再获得集合对象,最后查询数据。

但是这里有个不怎么“优雅”的问题,在于,每一个then()方法获取的对象,都是上一个then()方法返回的数据。而不能跨层访问。

什么意思,就是说在第三个then(blogs => {})中我们只能获取到查询的结果blogs,而不能使用上面的db对象和coll对象。这个时候,如果要打印出blogs列表后,要关闭数据库db.close()怎么办?

这个时候,可以两种解决方法:

第一种是,使用then()嵌套。我们将Promise链打断,使之嵌套,犹如使用回调函数的嵌套一般:

MongoClient.connect(url + db_name).then(db=> { let coll = db.collection('blogs'); coll.find().toArray().then(blogs=> { console.log(blogs.length); db.close(); }).catch(err=> { console.log(err); }); }).catch(err=> { console.log(err); })

这里我们将两个Promise嵌套,这样在最后一个查询操作里面,就可以调用外面的db对象了。但是这中方式,并不推荐。原因很简单,我们从一种回调函数地狱走向了另一种Promise回调地狱。

而且,我们要对每个Promise的异常进行捕捉,因为Promise没有形成链。

还有一种方式, 是在每个then()方法里都将db传过来:

MongoClient.connect(url + db_name).then(db=> { return {db:db,coll:db.collection('blogs')}; }).then(result=> { return {db:result.db,blogs:result.coll.find().toArray()}; }).then(result=> { return result.blogs.then(blogs=> { //注意这里,result.coll.find().toArray()返回的是一个Promise,因此这里需要再解析一层 return {db:result.db,blogs:blogs} }) }).then(result=> { console.log(result.blogs.length); result.db.close(); }).catch(err=> { console.log(err); });

我们在每个then()方法的返回中,都将db及其每次的其他结果组成一个对象返回。请注意,如果每次的结果都是一个同步的值还好说,但是如果是一个Promise值,每一个Promise都需要多做一层解析。

例如上面的一个例子,第二个then()方法返回的 {db:result.db,blogs:result.coll.find().toArray()} 对象中, blogs 是一个Promise,在下一个then()方法中,我们无法直接引用博客列表数组值,因此需要先调用then()方法解析一层,然后将两个同步值db和blogs返回。

注意,这里涉及到了Promise的嵌套,不过一个Promise只嵌套一层then()。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wygzfg.html