JavaScript 中,用 async + await 和直接同步方式执行有什么区别?意义是什么?

发布 05-14 阅读 10分钟

你一定要先理解以下重点内容:

你要把 JS 里执行分为两种。

第一种是实打实必须由 JS 线程执行的,比如做一些去算一个文件的 MD5 值,这种一般没人写异步,没好处,都是同步计算。

第二种不是由 JS 线程去执行,只是 JS 把任务提交给它,然后傻了吧唧的啥也不干等它完成并返回结果给自己。比如你调用浏览器接口去网络上下载图片,此时你的 JS 线程是把任务提交给了浏览器,然后一直等浏览器把结果返回给你。干活的是浏览器不是你 JS 线程。

对于不干活的情况,就得用异步的写法了,不然轻则执行效率低下,重则页面卡死,毕竟页面上那么多事等着 JS 线程去干,特别是现在的网站内容都是靠 JS 动态加载的。

以前使用的方法是回调,就是调用接口时同时传一个函数,告诉浏览器或谁执行完任务时调用函数,然后我们在函数中放一些需要执行完任务才能处理的事情。

比如让浏览器去下图片,我们就把显示图片放到函数中,这个函数就被称为回调函数。你们常见的什么 onload 、onready 都是这个意思。

$$edit$$
$$jsdemo$$
function download(callback) {
    // 这个下载花费 5 秒钟
    let file = "我是文件哦"
    setTimeout(() => callback(file), 5000)
}

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

// 下载好了通知我就行,不等你了
download((file) => {
    console.log("下载完成:" + file)
    console.log("显示图片")
})

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

// 开始执行了~
// 先去下个图片~
// 做其他事情去了~

// 5 秒后......

// 下载完成:我是文件哦
// 显示图片

但假如,我要下个文件,他的下载链接保存在 A文件里, A文件的下载链接保存在 B文件里, B文件的下载链接保存在 C文件里。

那我必须先下C文件,等C下完了拿到B的链接下B文件,再等B下完了去下A文件,最后才能在A文件里拿到我的文件下载链接。(没有这么蠢的场景,但多个任务依次依赖前一个任务完成才能处理的情况还是很常见的)

这样就会形成多层嵌套的回调,就被称为回调地狱。

$$edit$$
$$jsdemo$$
function download(callback) {
    // 这个下载花费 5 秒钟
    let file = "我是文件哦"
    setTimeout(() => callback(file), 5000)
}

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

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

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

于是乎,async + await 就出现了,它可以让我们用看起来像同步的方式去写异步的程序。

对于 async 和 await 千万不要恐怖,你把它们理解成回调的语法糖就行了,而回调函数的内容就是 await 之后的部分。

第一段的代码就可以改写成:

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

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

async function showImage() {
    let file = await download()
    // 以下是之前回调函数的内容
    console.log("下载完成:" + file)
    console.log("显示图片")
}

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

showImage()

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

// 开始执行了~
// 先去下个图片~
// 做其他事情去了~

// 5 秒后......

// 下载完成:我是文件哦
// 显示图片

不过乍看之下好像还略微复杂了一点,但如果改写第二个程序呢?

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

    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("做其他事情去了~")

回调地狱不见了,我们只用一个函数就完成了所有的内容,代码真是太清爽了,async + await 真是太厉害了。