async、await

async 与 await 是两个关键字,能让我们用同步的写法来写异步的程序。

async

async 关键字用于函数前面,它能使用函数返回的的值变成一个 promise:

$$jsdemo$$
$$edit$$
async function func() {
    return "三眼鸭"
}

let promise = func()
console.log(promise)

它与以下的代码是等价的:

$$jsdemo$$
$$edit$$
let promise = new Promise(function (resolve, reject) {
    resolve("三眼鸭")
})

console.log(promise)

单单是这样并不能体现 async 的作用, async 需要搭配 await 使用才能起到效果。

await

await 关键字表示等待 promise 执行完成并得到执行结果, await 只能在 async 函数中使用。

假设我们有一个使用 promise 语法的实例如下。

$$jsdemo$$
$$edit$$
const promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve("三眼鸭")
    }, 3000)
})

promise.then(function (result) {
    console.log(result) // 三眼鸭,等待执行完成
    console.log("执行完毕") // 执行完毕
})

使用 async、await 的语法改写如下。

$$jsdemo$$
$$edit$$
const promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve("三眼鸭")
    }, 3000)
})

async function func() {
    const result = await promise
    console.log(result) // 三眼鸭,等待执行完成
    console.log("执行完毕") // 执行完毕
}

func()

await 并不意味着会阻塞 JavaScript 的主线程的执行, 它阻塞的只是 async 函数的执行。

$$jsdemo$$
$$edit$$
let promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve("三眼鸭")
    }, 3000)
})

async function func() {
    const result = await promise
    console.log(result) // 三眼鸭,等待执行完成
    console.log("执行完毕") // 执行完毕
}

func()

console.log("END") // 最先执行

async、await 的好处

asyncawait 最大的好处就是让我们用同步的思维去写异步的程序。

假设我们有以下代码,要下载一个文件,这个文件的下载链接包含在 A 文件当中,而 A 文件的下载链接包含在 B 中, B 又包含在 C 中。

那么我们要依次下载 C → B → A → 最终文件,那么代码就得写成以下这样:

$$jsdemo$$
$$edit$$
function download() {
    // 这个下载花费 2 秒钟
    return new Promise((resolve, reject) => {
        let file = "我是文件哦"
        setTimeout(() => resolve(file), 2000)
    })
}

console.log("开始执行了~")
console.log("先去下个文件~")

// 下载好了通知我就行,不等你了
download().then((file) => {
    console.log("下载 C 完成")
    console.log("拿到 B 的链接")
    download().then((file) => {
        console.log("下载 B 完成")
        console.log("拿到 A 的链接")
        download().then((file) => {
            console.log("下载 A 完成")
            console.log("拿到 最终文件 的链接")
            download().then((file) => {
                console.log("下载 最终文件 完成")
            })
        })
    })
})

console.log("做其他事情去了~")

我们会陷入回调地狱中,回调越写越多,代码执行顺序越来越混乱。

当然,由于 then 中的返回值会作为下一个 then 的参数,因此我们可以改写如下。

$$jsdemo$$
$$edit$$
function download() {
    // 这个下载花费 2 秒钟
    return new Promise((resolve, reject) => {
        let file = "我是文件哦"
        setTimeout(() => resolve(file), 2000)
    })
}

console.log("开始执行了~")
console.log("先去下个文件~")

// 下载好了通知我就行,不等你了
download()
    .then((file) => {
        console.log("下载 C 完成")
        console.log("拿到 B 的链接")

        return download()
    })
    .then((file) => {
        console.log("下载 B 完成")
        console.log("拿到 A 的链接")
        return download()
    })
    .then((file) => {
        console.log("下载 A 完成")
        console.log("拿到 最终文件 的链接")
        return download()
    })
    .then((file) => {
        console.log("下载 最终文件 完成")
        return download()
    })

console.log("做其他事情去了~")

但依旧是不够简洁,那么就可以使用 asnycawait 改写我们的程序了:

$$jsdemo$$
$$edit$$
function download() {
    // 这个下载花费 2 秒钟

    return new Promise((resolve, reject) => {
        let file = "我是文件哦"
        setTimeout(() => resolve(file), 5000)
    })
}

async function getFile() {
    let file = await download()
    console.log("下载 C 完成")
    console.log("拿到 B 的链接")

    file = await download()
    console.log("下载 B 完成")
    console.log("拿到 A 的链接")

    file = await download()
    console.log("下载 A 完成")
    console.log("拿到 最终文件 的链接")

    file = await download()
    console.log("下载 A 完成")
    console.log("下载 最终文件 完成")
}

console.log("开始执行了~")
console.log("先去下个文件~")

getFile()

console.log("做其他事情去了~")

$$tip async、await 使得我们可以用同步的写法来写异步的代码,特别在于执行的结果是下一个执行的必需参数的时候。 $$

错误处理

await 返回的是 resolve 的结果,如果是 reject 则会抛出一个错误,我们需要处理这个错误的话则需要使用 try ... catch

$$jsdemo$$
$$edit$$
let promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
        reject(new Error("三眼鸭"))
    }, 3000)
})

async function func() {
    try {
        let result = await promise // throw new Error("三眼鸭")
        alert(result)
        alert("执行完毕")
    } catch (error) {
        alert(error) // Error: 三眼鸭
    }
}

func()

同理,我们在 async 函数中 throw 一个错误时就等同于 promise 中的 reject

$$jsdemo$$
$$edit$$
async function func() {
    // 等同于 promise 中的
    // reject("一个错误")
    throw "一个错误"
}

func().catch((error) => alert(error)) // 一个错误

练习

  1. func 的代码改成使用 async、await 的语法执行。
$$jsdemo$$
$$edit$$
function doAfter3s() {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve("三眼鸭的编程教室")
        }, 3000)
    })
}

function run() {
    // 改成使用 async、await 的方式执行
    doAfter3s().then((word) => alert(word))
}

func()

$$answer

$$jsdemo$$
$$edit$$
function doAfter3s() {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve("三眼鸭的编程教室")
        }, 3000)
    })
}

async function run() {
    const word = await doAfter3s()
    alert(word)
}

run()

$$

  1. 以下代码的打印顺序是什么,为什么?
function B() {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log("B")
            resolve()
        }, 1000)
    })
}

function D() {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log("D")
            resolve()
        }, 1000)
    })
}

async function run() {
    console.log("A")

    await B()

    console.log("C")

    D()

    console.log("E")
}

run()

console.log("F")

$$answer

$$jsdemo$$
$$edit$$
function B() {
    return new Promise(function (resolve) {
        setTimeout(function () {
            // 3. 距离 B() 一秒后执行
            console.log("B")
            resolve()
        }, 1000)
    })
}

function D() {
    return new Promise(function (resolve) {
        setTimeout(function () {
            // 6. 距离 D() 一秒后执行
            console.log("D")
            resolve()
        }, 1000)
    })
}

async function run() {
    // 1. 执行了 run() 首先执行这里
    console.log("A")

    // await 等同于把之后的所有代码放到 then 中
    // 因此这里只相当于执行了 B()
    // then 中的代码等到 B() resolve 时才执行
    B().then(function () {
        // 4. 此时 B() resolve 了
        console.log("C")

        // 仅是调用 D() 的 promise
        D()

        // 5. D() 没有 await、所以直接执行这里
        console.log("E")
    })
}

run()

// 2. run() 执行完成后执行
console.log("F")

$$