使用 async/await 的异步编程
上面讲解了 Node.js 中的回调函数,但回调只适用于简单的异步场景!当程序中有很多回调时,代码会变得非常复杂,而且调试也会很麻烦,因此在 ES2015 标准中新增了 Promise
特性,用来帮助处理异步代码而不涉及使用回调。在更高级的 ES2017 标准中,又新增了 async/await
语法,使得异步编程更加简单。本节将对如何使用 async/await
实现异步编程进行讲解。
Promise 基础
async/await
建立在 Promise
之上,因此要学习 async/await
,首先应该对 Promise
有所了解。
Promise
是 ES2015 标准中提供的一种处理异步代码(而不会陷入回调地狱)的方式,它本质上是一个对象,使用 new Promise()
构造函数可以创建该对象,new Promise()
构造函数中需要传入一个具有 resolve
和 reject
参数的函数,形式如下:
var p = new Promise(function(resolve, reject){
});
其中,resolve
表示异步操作执行成功后的回调函数(其参数通常用 data
表示),reject
表示异步操作执行失败后的回调函数(其参数通常用 err
表示)。Promise
对象共有 3 种状态。
-
pending
(进行中):Promise
对象刚被创建时的状态,表示异步操作还未完成。 -
fulfilled
(已完成):表示异步操作已经完成,并返回了一个值。 -
rejected
(已拒绝):表示异步操作失败,返回一个错误信息。
回调地狱是从英语 |
例如,定义一个 runAsync
函数,该函数中创建一个 Promise
对象,在程序执行 1 秒后输出一个字符串,代码如下:
function runAsync(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
console.log('执行异步操作1')
resolve('promise1')
}, 1000)
})
return p
}
runAsync()
运行上面代码后,会输出 “执行异步操作1”,但其中的 resolve('promise1')
并没有执行,它的作用是什么呢?
前面我们提到 resolve
是异步操作执行成功后要执行的回调函数,那么它如何执行呢?Promise
对象提供了 then
方法,用来指定执行 resolve
回调。
例如,下面的代码使用上面创建的 Promise
对象,并在 then
方法中执行 resolve
回调:
runAsync().then(function(data){
console.log(data)
})
运行上面代码,会输出以下结果:
执行异步操作1
promise1
从上面的示例可以看出,then
方法中的函数就类似于一个回调函数,但它能够在异步操作完成之后被执行,这就是 Promise
的好处,它能够将原来的回调函数分离出来,在异步操作执行完后,再去执行回调函数。
另外,使用 Promise
实现异步还有一个最大的特点:链式调用回调函数,即它可以在 then
方法中继续创建 Promise
对象并返回,然后继续调用 then
来进行回调操作。
例如,按照上面 runAsync
函数的方式再定义两个 runAsync2
和 runAsync3
函数,代码如下:
runAsync()
.then(function(data){
console.log(data)
return runAsync2()
})
.then(function(data){
console.log(data)
return runAsync3()
})
.then(function(data){
console.log(data)
})
运行上面代码,结果如下:
执行异步操作1
promise1
执行异步操作2
promise2
执行异步操作3
promise3
上面我们讲解了使用 then
方法可以执行 resolve
回调,那么 reject
回调如何执行呢?reject
的作用是把 Promise
的状态设置为 rejected
,我们同样可以在 then
方法中执行。
例如,修改上面定义的 runAsync
函数,其中定义一个 flag
变量,默认为 false
,判断 flag
为 true
时,使用 resolve
回调传递值,否则,使用 reject
回调传递值。代码如下:
function runAsync(){
flag=false
var p = new Promise(function(resolve, reject){
setTimeout(function(){
if(flag){
console.log('执行异步操作')
resolve('promise')
}
else
reject('执行异步操作失败')
}, 1000)
})
return p
}
然后在 Promise
对象的 then
方法中分别执行 resolve
回调和 reject
回调,代码如下:
runAsync()
.then(function(data){
console.log(data);
},
function(err){
console.log(err);
})
运行上面修改后的代码,由于 flag
变量为 false
,所以输出结果为:
执行异步操作失败
除了 then
方法,Promise
对象还提供了一个 catch
方法,也可以执行 reject
回调,其使用方法与 then
类似。例如,上面代码可以修改如下:
runAsync()
.then(function(data){
console.log(data);
})
.catch(function(err){
console.log(err);
});
为什么使用async/await
ES2015 中引入 Promise
主要是为了解决异步回调的问题,但是由于它自身语法的复杂性,在 ES2017 标准中引入了 async/await
。async/await
减少了 Promise
的样板,并且减少了 Promise
链式调用的 “不破坏链条” 的限制,它使得代码看起来像是同步的,但它是异步的并且在后台无阻塞。因此,通过使用 async/await
实现异步编程是一种更好的方式。
async/await的使用
通过前面的讲解,我们知道 ES2015 标准下的异步函数会返回 Promise
,例如下面的代码:
const AsyncOper = () => {
return new Promise(resolve => {
setTimeout(() => resolve('执行操作'), 1000)
})
}
在使用 async/await
对上面代码进行异步回调时,只需要在声明的函数前面加上 async
关键字,并在要调用的函数名前面加上 await
即可。这里需要注意的是,客户端函数必须被定义为 async
。例如,下面代码中,要异步调用上面定义的 AsyncOper
函数,首先需要使用 async
关键字定义一个匿名的函数,然后在要调用的 AsyncOper
函数前面加上 await
关键字,代码如下:
const useAsync = async () => {
console.log(await AsyncOper())
}
Node.js 中,在任何函数之前加上
|
【例9.3】使用 async/await
执行异步回调。(实例位置:资源包\源码\09\03)
在 WebStorm
中创建一个 .js
文件,其中主要演示使用 async/await
执行异步回调操作,代码如下:
var fs = require("fs") //引入fs模块
//定义异步函数,用来判断是否为文件夹
async function isDir(path) {
return new Promise((resolve, reject)=> {
fs.stat(path, (err,stats)=> {
if (err) { //如果发生错误,则返回
return
}
if (stats.isDirectory()) { //如果是文件夹返回true
resolve(true);
}else { //否则返回false
resolve(false);
}
})
})
}
var path = "D:\\测试文件夹" //指定当前路径
var dirArr = [] //用来记录遍历得到的所有文件夹名称
fs.readdir(path, async (err, data) => {
if (err) {
return
}
//遍历指定目录
for (var i = 0; i < data.length; i++) {
//异步调用isDir函数,判断是否为文件夹
if (await isDir(path + '/' + data[i])) {
dirArr.push(data[i]) //将文件夹的名称添加到数组中
}
}
console.log(dirArr) //输出所有文件夹名称
})
代码中所指定的 D 盘测试文件夹中的原始内容如图9.8所示,运行程序,效果如图9.9所示,从结果可以看出,本程序只输出了指定路径下的所有文件夹名称。


使用async/await异步编程的优点
本节讲解了两种异步编程的方式,分别是 Promise
和 async/await
。async/await
与 Promise
相比,有很多优点,主要如下:
-
Promise
的出现解决了传统回调函数导致的 “地狱回调” 问题,但它的语法导致其发展成一个回调链,遇到复杂的业务场景时,这样的语法是不美观的;async/await
代码看起来更加简洁,使得异步代码看起来像同步代码,而await
的本质其实就是可以提供等同于同步效果的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。 -
被
async
修改的函数会默认返回一个Promise
对象的resolve
值,因此对async
函数可以直接使用then
方法,返回值就是then
方法传入的函数。 -
async/await
是基于Promise
实现的,可以说是改良版的Promise
,它不能用于普通的回调函数。 -
async/await
与Promise
一样,是非阻塞的。