日期:2024年10月22日

async和await

前边回答了关于Promise的问题,又有同学提出了新的问题:async和await是干什么用的?这节课咱们来解释一下。

async

学习了Promise后,我们可以便可以像这样创建一个异步函数:

function fn(){
    return new Promise(resolve => {
        resolve(10)
    })
}

上例中,函数功能比较简单,即调用函数后返回了一个数字10(通过Promise异步返回)。但是不管返回什么样的结果,异步函数本身的返回值必须得是Promise才能称得上是一个异步函数。但是很明显,如果你的异步函数只是返回一个单个的值(比如10)却因此要编写创建Promise的一大串代码显然是划不来的。

为了解决这个问题,JS为我们提供了async关键字。只需要在创建函数时,在function前使用async关键字,即可使一个普通函数升级为异步函数,像是这样:

async function fn2(){
    return 10
}

上例中fn2使用了async来声明,此时fn2变成为了一个异步函数。异步函数的第一个特点便是它的返回值会自动被包装为一个Promise,上例中fn2的返回值虽然是10,但是如果你尝试去接收的话,你会发现它返回的是一个Promise。这样看来,fn1和fn2的功能是一模一样的,不同点在于fn1的Promise是我们手动创建的,而fn2的却是自动创建的。

简而言之,async简化了异步函数的创建,省略了部分Promise的创建工作。但如果仅仅是这样那async的功能就显得十分的鸡肋了,因为这种简化只适用于单个返回结果的异步函数,如果函数功能比较复杂依然还是需要手动创建Promise。所以async还有第二个特点:在async声明的函数中可以使用await关键字。这是怎么回事?不急,下一小节我们介绍,这里先总结一下async的特点:

  1. async创建的函数其返回值会自动封装到一个Promise中返回
  2. async创建的函数中可以使用await关键字

await

调用一个异步函数时,我们通常得这样:

fn2()
    .then(num => console.log(num))
    .catch(err => console.log("出错了"))

这种方式并不优雅,且当我们需要同时调用多个异步函数时,我们必须要一直then下去,这样异步函数的调用就变得更加的不优雅了:

async function fn2(){
    return 10
}

async function fn3(num){
    return num + 5
}

async function fn4(num){
    return num + 10
}

async function fn5(num){
    return num + 20
}

fn2()
    .then(fn3)
    .then(fn4)
    .then(fn5)
    .then(console.log)
    .catch(err => console.log("出错了"))

当你异步用得久了,你会发现你非常的怀念之前同步代码的编写方式,一行一行的,顺序特别的清晰,逻辑也比较容易理解,像是这样:

let result = fn2()
result = fn3(result)
result = fn4(result)
result = fn5(result)
console.log(result)

但是不行啊,上边的几个函数都是异步函数,返回值都是Promise,我这么写等于是将Promise作为参数传递,虽然能够执行,但是结果肯定不对啊!嘿嘿,为了解决我们这些既喜欢同步代码的编写方式,又想要使用异步的人,JS为我们提供了一个await关键字,让我们的理想照进了现实。

调用异步函数时除了可以通过then方法来读取结果以外,我们也可以直接在调用时使用await关键字,像是这样:

let result = await fn2()

这样调用后,异步函数会在有执行结果后,将执行结果返回(如果有错误就自动报错)。但是,你千万不要着急去尝试这行代码,因为这种方式一眼看上去就不对!fn2是异步的,它不会立刻返回结果,正常来讲它会在其他代码(它下边的代码)执行完才会返回结果,所以它应该返回的是一个Promise而不应该是一个有效值。如果它真的返回了一个有效的值,这也就意味着,它返回结果前,其后的代码是不能执行的,那么这也就失去了异步本身的意义了(会阻塞其他代码的执行)!

所以注意了await并不能任意的使用,因为await并不会改变异步的本质,它只是改变了异步的调用方式,让我们像编写同步代码一样去使用异步。而不是将异步改回到同步(真是这样的话就得不偿失了)。那么在哪里能够使用await呢?有两个地方:

  1. async关键字创建的函数
  2. JS模块的最外层作用域

像这样:

async function fn6(){
    try{
        let result = await fn2()
        result = await fn3(result)
        result = await fn4(result)
        result = await fn5(result)
        console.log(result)
    }catch(e){
        console.log("出错了")
    }
    
}

fn6()

或者是匿名的异步函数:

(async ()=>{
    try{
        let result = await fn2()
        result = await fn3(result)
        result = await fn4(result)
        result = await fn5(result)
        console.log(result)
    }catch(e){
        console.log("出错了")
    }
})()

或者是JS模块中:

<script type="module">    
    try{
        let result = await fn2()
        result = await fn3(result)
        result = await fn4(result)
        result = await fn5(result)
        console.log(result)
    }catch(e){
        console.log("出错了")
    }
</script>

await的执行原理并不难理解,因为await必须写在异步函数中或模块中,而异步函数和模块本身就是异步的,所以当执行到await的代码时,异步函数(或模块)会暂停执行,等到await标识的异步代码有结果时在继续执行函数(或模块)。

因为调用方式的改变,错误的处理方式也不再是catch方法,而是在同步调用中所使用的try-catch。

学习完了await我们也就理解了,async的第二个作用,相较于简化异步函数的创建能够使用await才是async函数的最大作用。当然无论是await调用还是直接调用,并没有本质区别,只是调用方式的不同,所以具体使用哪种,看你自己!

下载地址:

链接:https://pan.baidu.com/s/14Cqjzx4I_TMjeFf4GBIkBg?pwd=yvft
提取码:yvft

5 6 投票数
文章评分
订阅评论
提醒
guest

2 评论
最旧
最新 最多投票
内联反馈
查看所有评论
马化腾
马化腾
2 年 前

太棒了

pandajia
pandajia
1 年 前

bucuo

2
0
希望看到您的想法,请您发表评论x