【JavaScript】async & await | 是什么 | 怎么用 | 与Promise的对比

94 阅读3分钟

前言

本文主要内容:通过多个使用 async & await 的函数与使用 Promise 的函数之间的对比,进一步理解 async & await 作为 Promise 语法糖的功能与效果

什么是 async

在介绍什么是 async 前,让我们回顾一下上节课讲过的 Promise,如下

function fn1() {
    return Promise.resolve(1);
}
fn1().then(r => {
    console.log(r);
})

fn1函数返回了一个 Promise 对象,这个 Promise 对象里异步保存了一个数据 1,当我们要读取这个异步函数时,使用了 then 方法,从而获得了这个值 1

接下来让我们看看 async 是如何实现这段代码的

async function fn2() {
    return 1;
}
fn2().then(r => {
    console.log(r);
})

可以看到fn2函数前加了 async,返回值是 1,但是却能够调用 then 方法

所以我们通过console.log(fn2());可以获得如下结果

image.png

果然返回值不是一个普通的 1,而是 resolve(1) 的 Promise 对象

那么我们就能大致给出 async是什么 的回答

async 是 Promise 的一个语法糖,可以更快捷、更方便地创建一个异步函数,返回值会自动被封装到一个Promise 对象中

什么是 await

再来看一段用 Promise 实现的代码,如下

function sum(a, b) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(a+b)
        },1000);
    })
}
async function fn3() {
    sum(3, 3).then(r => {
        console.log(r)
    })
}

可以看到 sum() 是一个异步函数,而 fn3() 调用了它,并且通过 then 方法获取了数据

现在用 await 来实现一下相同的结果

async function fn4() {
    let result = await sum(3, 3);
    console.log(result);
}

可以看到没有用 then 方法,反而仅仅用赋值运算符'='完成了result的赋值

也就是,Promise 解决了异步调用中回调函数问题,async 实现了以同步的方式调用异步的代码的功能

如果上面的还不太容易看出来,再看看下面这个链式调用

function fn3() {
    sum(3, 3)
    .then(r => sum(r, 4))
    .then(r => sum(r, 5))
    .then(r => {
        console.log(r);
    })
}

而使用 async & await 是这样的

function fn4() {
    let result = await sum(3, 3);
    result = await sum(result, 4);
    result = await sum(result, 5);
    console.log(result);
}

这样就很容易看出,我们用同步的方式调用了异步的函数

async & await 的作用范围

接下来让我们进一步了解 async & await

不加 await

来看这么个问题,以下代码的输出是

async function fn4() {
    console.log(1);
    console.log(2);
    console.log(3);
}
fn4();
console.log(4);

image.png

是的,当我们不添加 await 时,程序正常的一行一行执行,它的等效 Promise 代码如下

function fn5() {
    return new Promise(resolve => {
        console.log(1);
        console.log(2);
        console.log(3);
        resolve();
    })
}

可以看到我们返回的 Promise 对象是没有异步存储值的,而只是打印了 1、2、3

加上 await

那么当我们加上 await 时,程序的输出又会变成什么样呢,如下

async function fn4() {
    console.log(1);
    await console.log(2);
    console.log(3);
}
fn4();
console.log(4);

image.png

可以看到,3被异步打印了出来

这是因为使用await调用函数后,当前函数后边的所有代码会在当前函数执行完毕后,被放入微任务队列中。所以 await 使得

  • 1 是正常被打印的
  • 2 是当前任务打印的
  • 3 被放入了微任务队列,在 4 之后打印

那么来看看使用 Promise 的写法如何

function fn5() {
    return new Promise(resolve => {
        console.log(1);
        console.log(2);
        resolve();
    }).then(r => {
        console.log(3);        
 })
}

也就是 3 被 then 方法放入微任务队列里异步执行了

小结

  • async 是 Promise 的语法糖,可以更快捷、方便地定义异步函数

  • 当我们通过await去调用异步函数时,它会暂停代码的运行,直到异步代码执行有结果,才会将结果返回,然而这并不意味着程序会被阻塞,await 只能用于 async 声明的异步函数中,或 ES模块的顶级作用域中,从而避免阻塞其他程序代码的执行