使用 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一样,是非阻塞的。