async/await

1,352 阅读4分钟

为什么使用 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原理。如有错误或不严谨的地方欢迎批评指正。