首页 > web前端 > js教程 > 正文

什么是async/await?异步编程的语法糖

星降
发布: 2025-08-16 13:19:01
原创
200人浏览过
async/await是JavaScript异步编程的语法糖,基于Promise实现,通过同步式写法简化异步流程。async函数返回Promise,await暂停函数执行直至Promise完成,提升代码可读性与维护性。它避免回调地狱和长链式Promise,用try...catch统一处理错误,并借助事件循环非阻塞主线程。关键实践包括:勿忘await、合理捕获错误、并行任务用Promise.all()、避免顶层await兼容问题。

什么是async/await?异步编程的语法糖

async/await,在我看来,它就是JavaScript异步编程领域里的一剂“后悔药”,专门用来治愈我们那些年被回调函数和Promise链条折磨得头昏脑涨的程序员们。它本质上是Promise的语法糖,让我们写异步代码的时候,能用一种更接近同步的、命令式的写法,大幅提升代码的可读性和可维护性。你不再需要层层嵌套的回调,也不用担心Promise链条过长导致的代码迷宫,一切都变得直观起来。

解决方案

要理解async/await,我们得从它的核心概念入手。简单来说,

async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
关键字用于定义一个异步函数,这个函数总是会返回一个 Promise。而
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
关键字,则只能在
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数内部使用,它的作用是“等待”一个 Promise 解决(resolve)或拒绝(reject)。当
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
遇到一个 Promise 时,它会暂停当前
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数的执行,直到那个 Promise 完成。一旦 Promise 完成,
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
就会返回其解决的值,或者抛出其拒绝的原因。

想象一下,你正在做饭,需要先烧水,再下面条。传统的异步可能就是你点火烧水,然后设置一个定时器,等水开了再回来下面。用Promise,就是

烧水().then(下面条)
登录后复制
。但有了async/await,你可以这样写:

async function 做饭() {
  try {
    console.log('开始做饭...');
    const 水开了 = await 烧水(); // 看起来就像同步代码,但实际上在等待
    console.log(水开了); // 输出 "水烧开了!"
    const 面条煮好了 = await 下面条(); // 等待面条煮好
    console.log(面条煮好了); // 输出 "面条煮好了!"
    console.log('饭做好了,开吃!');
  } catch (错误) {
    console.error('做饭过程中出错了:', 错误);
  }
}

// 模拟异步操作
function 烧水() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('水烧开了!');
    }, 2000);
  });
}

function 下面条() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('面条煮好了!');
    }, 1500);
  });
}

做饭();
登录后复制

这段代码看起来就像同步执行一样,一步一步来,但实际上,

烧水()
登录后复制
下面条()
登录后复制
都是异步操作,它们不会阻塞主线程。
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
只是暂停了
做饭
登录后复制
登录后复制
函数的执行,将控制权交还给事件循环,让其他任务有机会运行。等到
烧水
登录后复制
Promise 解决了,
做饭
登录后复制
登录后复制
函数才会从暂停的地方继续执行。这种“暂停-恢复”的模式,正是async/await的魅力所在。

async/await 如何彻底改变异步编程体验?

说实话,作为一名开发者,我个人觉得async/await最大的贡献,就是它把异步代码的“心智负担”降到了一个前所未有的低点。在它出现之前,我们处理异步,要么是回调函数一层套一层,形成臭名昭著的“回调地狱”(callback hell),代码可读性极差,错误处理也极其痛苦;要么是Promise链式调用,虽然解决了回调地狱的问题,但当业务逻辑复杂,需要大量顺序执行的异步操作时,Promise的

.then().then().then()
登录后复制
也会变得相当冗长,像一条长长的“Promise链狱”,一眼望去,逻辑流依然不够清晰。

async/await的出现,就像是给Promise穿上了一件“隐身衣”,让那些原本分散在

.then()
登录后复制
回调里的逻辑,能够以我们最熟悉的、自上而下的同步方式呈现。你不再需要去思考“这个Promise解决了之后,我要在它的回调里做什么”,而是可以直接写“等待这个结果,然后用这个结果去做下一件事”。这种思维模式的转变是颠覆性的。它把异步操作的复杂性隐藏起来,让开发者能更专注于业务逻辑本身,而不是异步机制的实现细节。错误处理也变得简单,一个普通的
try...catch
登录后复制
登录后复制
登录后复制
块就能搞定所有
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
表达式可能抛出的错误,这比Promise的
.catch()
登录后复制
登录后复制
要直观和集中得多。对我来说,这简直是生产力上的巨大解放。

async/await 的底层机制:Promise 状态机的优雅舞蹈

很多人会觉得async/await很“魔法”,仿佛它真的能让JavaScript变成同步语言。但实际上,它背后并没有什么黑科技,一切都还是基于Promise和JavaScript的事件循环机制。当你定义一个

async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数时,JavaScript引擎会把它转换成一个返回Promise的函数。这个Promise的状态会随着
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数的执行而改变:如果函数正常返回,Promise就resolve;如果函数抛出错误,Promise就reject。

await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
关键字,它所做的,可以粗略地理解为:当它遇到一个Promise时,它会“暂停”当前
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数的执行,并将该Promise注册到事件循环的微任务队列中。此时,控制权会立即交还给调用
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数的上下文,主线程得以继续执行其他任务。一旦
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
等待的那个Promise状态变为resolved或rejected,事件循环会在合适的时机(当前宏任务执行完毕后,微任务队列清空时)重新调度
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数的剩余部分继续执行。这就像一个精巧的“状态机”,在幕后默默地调度着代码的执行流程。

所以,

await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
并不是阻塞线程,它只是暂停了当前
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数的执行,让出CPU给其他任务。这种非阻塞的等待机制,正是JavaScript单线程异步能力的体现。理解这一点很重要,它能帮助我们避免一些常见的误解,比如认为
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
会让整个应用卡死。它不会,它只是让出控制权,等待时机成熟再回来。这就像你在等一份外卖,你不会一直盯着门,而是可以去做别的事情,直到门铃响了再去取餐。

避免 async/await 陷阱:提升异步代码质量的实践指南

async/await虽然好用,但用起来也有些需要注意的地方,否则可能会踩坑。我见过不少人,包括我自己,刚开始用的时候就犯过一些“低级”错误。

一个最常见的误区是忘记

await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。如果你在一个
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数里调用了另一个返回Promise的函数,但没有加
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,那么你得到的将是一个Pending状态的Promise,而不是它最终的结果。这会导致后续依赖这个结果的逻辑出错,而且因为没有错误抛出,调试起来会比较头疼。

另一个问题是错误处理。虽然

try...catch
登录后复制
登录后复制
登录后复制
看起来很直观,但如果你在
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
了一个Promise之后,没有捕获它的错误,那么这个错误可能会变成一个未处理的Promise拒绝(unhandled promise rejection),导致程序崩溃或者产生意想不到的行为。所以,几乎所有
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
表达式,都应该被包裹在
try...catch
登录后复制
登录后复制
登录后复制
块中,或者确保其Promise链条末端有
.catch()
登录后复制
登录后复制
处理。

此外,对于需要并行执行的异步操作,不要盲目地一个接一个

await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。比如,你需要同时请求用户数据和订单数据,它们之间没有依赖关系。如果你写成
const user = await fetchUser(); const order = await fetchOrder();
登录后复制
,那么
fetchOrder
登录后复制
会等到
fetchUser
登录后复制
完成后才开始。这无疑浪费了时间。正确的做法是使用
Promise.all()
登录后复制
来并发执行它们:
const [user, order] = await Promise.all([fetchUser(), fetchOrder()]);
登录后复制
这样能大大提高效率。

最后,记得

await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
只能在
async
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
函数内部使用。如果你想在顶层作用域(比如一个模块的根部)使用
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,在旧版本的Node.js或浏览器中,你需要将其包裹在一个立即执行的异步函数表达式(IIFE)中,例如
(async () => { await someFunc(); })();
登录后复制
。不过,现在很多新环境已经支持顶层
await
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
了,这让代码更加简洁。掌握这些小细节,能让你的async/await代码更加健壮和高效。

以上就是什么是async/await?异步编程的语法糖的详细内容,更多请关注php中文网其它相关文章!

编程速学教程(入门课程)
编程速学教程(入门课程)

编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号