为什么使用 async/await
- 用同步的方式写异步代码, 便于理解。
- 请求之间有依赖关系。例如: b请求依赖于a请求返回的结果作为参数。
基本用法
参考es6.ruanyifeng.com/#docs/async
async
函数返回一个 Promise
对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。正常情况下,await
命令后面是一个 Promise
对象,返回该对象的结果。如果不是 Promise
对象,就直接返回对应的值。
使用
1. 只有在 await
后面跟的是一个Promise
对象的时候,才会阻断后面的代码执行, 否则就像普通语句一样,没有阻断效果。
例子1: 假设有一个函数 a 获取数据需要1秒, b 调用 a。
// a 假设是一个异步获取数据的请求(不是Promise) 需要耗时1000ms
function a(){
console.log("开始获取数据...")
setTimeout(()=>{
console.log('获取完毕!')
}, 1000)
}
// b 调用 a
async function b(){
console.log('进程开始')
await a()
console.log('进程结束')
}
b()
// 打印顺序为
进程开始
开始获取数据...
进程结束
获取完毕!
可以看到 "获取完毕!" 在 "进程结束" 之后才打印出来。而我们的预期为 进程开始>开始获取数据...>获取完毕>进程结束!
现在我们将 a 函数改造为返回一个Promise
。
// a 假设是一个异步获取数据的请求(是Promise) 需要耗时1000ms
function a(){
console.log("开始获取数据...")
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('获取完毕...')
resolve()
}, 1000)
})
}
// b 调用 a
async function b(){
console.log('进程开始')
await a()
console.log('进程结束')
}
b()
// 打印顺序为
进程开始
开始获取数据...
获取完毕!
进程结束
现在结果就是按照我们预期的顺序打印了。
2. 继发与并发
例子2:一个函数里,包含多个async函数,每个async函数里都有await
// a,b,c 三个函数都为需要1秒获取数据的异步请求 d调用a,b,c并按顺序打印出结果
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a的返回值')
}, 1000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('b的返回值')
}, 1000)
})
}
function c() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('c的返回值')
}, 1000)
})
}
async function d() {
let dataA = await a();
console.log(dataA);
let dataB = await b();
console.log(dataB);
let dataC = await c();
console.log(dataC)
}
d()
// d执行结果为
a的返回值
b的返回值
c的返回值
a,b,c的结果按顺序打印, 并且每个函数会等到上一个函数执行完成才开始进行, 所有操作都是继发。如果a,b,c三个请求没有先后的依赖关系,这样继发的效率很差,非常浪费时间。我们需要的是并发发出请求并将结果按顺序依次输出。
改造上面的例子,使请求并发,结果依次打印。
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a的返回值')
}, 2000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('b的返回值')
}, 1000)
})
}
function c() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('c的返回值')
}, 1000)
})
}
// 用Promise.all方法实现并发请求
function promiseAll() {
Promise.all([a(), b(), c()]).then(res => {
console.log(res)
})
}
// res打印结果 (注意: 此例子中 a 的延迟已经改为 2000ms, Promise.all依然会按顺序输出)
["a的返回值", "b的返回值", "c的返回值"]
async/await 用在循环中的坑
// 有三个人 每人要吃一个苹果
let peopleList = ['大地瓜', '虎皮猫', '小不点']
// 食物列表初始值为空
let foodList = [];
// 获取食物信息
function getFood() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('苹果')
}, 1000)
})
} // 根据人物列表循环获取食物, 一人获取一个苹果
peopleList.forEach(async people => {
let food = await getFood()
foodList.push(food)
})
// 打印食物列表
console.log(foodList) // 输出结果为[]
forEach
加上了async/await
后,由于await
只会阻断async
的函数体内代码的执行,最后的console.log
并不会等待async
函数体内的代码执行完就直接进行了打印,此时foodList
还在1秒的等待时间,未执行Push
方法。将打印食物列表 加上定时器才能打印出结果。显然不能用这种方法处理结果。
// 不能用这种方法
setTimeout(() => {
console.log(foodList)
},1000)
//输出 ["苹果", "苹果", "苹果"]
想要等待异步获取的数据返回之后,再打印出foodList,要用for循环。
// 有三个人 每人要吃一个苹果
let peopleList = ['大地瓜', '虎皮猫', '小不点']
// 食物列表初始值为空
let foodList = [];
// 获取食物信息
function getFood() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('苹果')
}, 1000)
})
}
// 用一个h函数作为 async的载体
async function h() {
//根据人物列表循环获取食物, 每次获取一个苹果
for (let i = 0; i < peopleList.length; i++) {
let food = await getFood()
foodList.push(food)
}
// 打印食物列表 放到h函数里
console.log(foodList) // ['苹果', '苹果', '苹果']
}
注意: 此处的console.log在async
函数体内,所以会等待await
结果。
最后
以上内容只是停留在使用层面, 未提及async/await
原理。如有错误或不严谨的地方欢迎批评指正。