0

0

什么是JS的async/await?

小老鼠

小老鼠

发布时间:2025-08-30 16:14:01

|

725人浏览过

|

来源于php中文网

原创

async/await是基于Promise的语法糖,使异步代码更像同步,提升可读性和错误处理能力,但需注意避免遗漏await、过度串行化及循环中滥用等问题,合理使用Promise.all实现并发,理解其底层仍依赖事件循环与Promise机制。

什么是js的async/await?

JavaScript 中的

async/await
是一对语法糖,它建立在 Promise 之上,目的是让我们能够以一种更接近同步代码的方式来编写和理解异步代码,从而避免回调地狱(Callback Hell)和复杂的 Promise 链式调用,让异步流程的控制变得直观且易于维护。它并没有改变 JavaScript 单线程、非阻塞的本质,只是提供了一种更优雅的异步处理方案。

解决方案

在我看来,

async/await
的出现,简直是前端异步编程领域的一剂强心针。回想当年,处理异步操作,我们从回调函数一路摸索到 Promise,每一步都是为了让代码更可读、更可控。而
async/await
,则直接把异步代码的“面貌”拉回了同步的舒适区。

简单来说,

async
关键字用于声明一个函数是异步的。这个函数会默认返回一个 Promise 对象。当你在
async
函数内部使用
await
关键字时,它会暂停当前
async
函数的执行,直到其后面的 Promise 对象状态变为 resolved(成功)或 rejected(失败)。一旦 Promise 解决,
await
就会返回 Promise 的值;如果 Promise 拒绝,它就会抛出异常。

这就像是你在厨房里做饭,你不能一直等着水烧开才切菜。传统的回调就像是“水开了叫我一声,我再回来切菜”,而 Promise 就像是“我先给你个承诺,水开了我给你个结果”。

async/await
呢?它更像是“我就站在水壶旁边盯着,水开了我就去切菜,期间我啥也不干,但其实我并没有真的把整个厨房都停下来,只是我自己的注意力被锁定了”。

让我们看一个简单的例子:

// 传统回调
function fetchDataCallback(callback) {
    setTimeout(() => {
        callback("数据已获取 (回调)");
    }, 1000);
}

// fetchDataCallback(data => console.log(data));

// Promise 链式调用
function fetchDataPromise() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve("数据已获取 (Promise)");
        }, 1000);
    });
}

// fetchDataPromise().then(data => console.log(data));

// async/await
async function fetchDataAsyncAwait() {
    try {
        console.log("开始获取数据...");
        const data = await fetchDataPromise(); // 暂停当前函数执行,等待 Promise 解决
        console.log(data + " (async/await)");
        console.log("数据获取完成,继续执行后续操作。");
        return "完成";
    } catch (error) {
        console.error("获取数据失败:", error);
        throw error; // 重新抛出错误,让外部捕获
    }
}

// fetchDataAsyncAwait().then(status => console.log("函数执行状态:", status));
// 或者在一个立即执行的 async 函数中调用
(async () => {
    await fetchDataAsyncAwait();
})();

在这个

fetchDataAsyncAwait
函数里,
await fetchDataPromise()
这一行,让整个函数的执行看起来就像是同步的。它会“等待”1秒,然后才执行下一行
console.log(data + " (async/await)")
。这种直观性,对于理解复杂的异步流程,简直是救命稻草。

async
函数与
await
表达式的核心工作原理是什么?

要深入理解

async/await
,就不能只停留在“看起来像同步”的表象。它的核心魔法,在于 JavaScript 引擎在幕后对
async
函数进行了一个巧妙的转换。一个
async
函数,在编译时会被转换成一个状态机(State Machine)。每当你遇到一个
await
表达式,这个状态机就会“暂停”当前函数的执行,并将控制权交还给事件循环(Event Loop)。

具体来说,当

await
遇到一个 Promise 时:

  1. 它会注册一个回调函数到这个 Promise 的
    .then()
    方法上。这个回调函数包含了
    await
    之后的所有代码。
  2. 然后,
    async
    函数会立即返回一个 Promise,并将控制权交回给调用栈。这意味着主线程并没有被阻塞。
  3. await
    等待的 Promise 解决(resolved)时,之前注册的回调函数就会被放入微任务队列(Microtask Queue)。
  4. 事件循环在当前宏任务执行完毕后,会优先处理微任务队列中的任务。当轮到这个回调函数执行时,
    async
    函数会从之前暂停的地方恢复执行,并拿到 Promise 的解决值。

如果

await
后面的不是一个 Promise,JavaScript 会将其立即包装成一个已解决的 Promise。如果 Promise 拒绝(rejected),那么
await
表达式会抛出一个错误,这个错误可以通过
try...catch
块来捕获,这与同步代码的错误处理机制非常相似,极大地提升了错误处理的直观性。

所以,

async/await
并没有引入新的底层异步机制,它只是在 Promise 的基础上,提供了一层语法糖,让开发者能够以更线性的思维去组织和阅读异步逻辑。这就像是给 Promise 穿上了一件“同步代码”的外衣,让它看起来更亲切,但骨子里它还是那个处理异步的 Promise。

手机在线人工冲值
手机在线人工冲值

说明:我不知道这个系统还能用到什么地方!他的运作方式是这样的,客户在其他地方比如掏宝购买了 你得卡,然后在你的网站进行冲值,你得有人登陆并看着后台,如果有人冲值,就会刷出记录,手工冲值完毕后,你得点击 [冲值完毕],客户的页面 就会返回 冲值信息!安装:上传所有文件,倒入(sql.txt)mysql数据库,使用myphpadminphplib 777phplib/sys.php 777phplib

下载

在实际开发中,
async/await
常见的陷阱与最佳实践有哪些?

尽管

async/await
极大地方便了异步编程,但在实际应用中,如果不注意,也可能踩到一些坑,或者无法充分发挥其优势。

常见的陷阱:

  1. 遗漏
    await
    关键字:
    这是最常见的错误之一。如果你在
    async
    函数中调用了一个返回 Promise 的函数,但忘记了
    await
    ,那么你得到的将是一个未决的 Promise 对象,而不是它最终的值。例如
    const result = someAsyncFunction();
    而不是
    const result = await someAsyncFunction();
    。这会导致你的
    result
    变量实际上是一个 Promise,而不是你期望的数据,后续操作会出错。
  2. 过度串行化:
    await
    的行为是暂停当前
    async
    函数的执行,等待 Promise 解决。如果你有多个相互独立的异步操作,却一个接一个地
    await
    它们,那么这些操作就会串行执行,白白浪费了异步并发的优势。比如:
    // 效率低下,两个请求会依次等待
    const user = await fetchUser();
    const posts = await fetchUserPosts(user.id);
  3. 错误处理不当: 虽然
    try...catch
    使得错误处理直观,但如果忘记使用,或者在
    await
    之前就抛出了同步错误,那么
    async
    函数返回的 Promise 就会直接拒绝,而你可能没有相应的
    .catch()
    来处理。此外,对于多个
    await
    操作,如果只想捕获其中某个的错误,或者想在错误发生后继续执行其他操作,
    try...catch
    的粒度需要仔细考量。
  4. 在循环中滥用
    await
    for
    循环中使用
    await
    会导致循环体内的异步操作串行执行,这在处理大量数据时会非常慢。
    // 糟糕的实践,会逐个等待
    for (const item of items) {
        await processItem(item);
    }

最佳实践:

  1. 始终使用
    await
    确保你真正需要等待 Promise 解决后再进行下一步操作。如果你不需要等待,或者想在后台启动一个异步任务,就不要使用
    await
  2. 利用
    Promise.all()
    实现并发:
    对于相互独立的异步操作,使用
    Promise.all()
    将它们并行执行,然后一次性
    await
    它们的结果,可以显著提高效率。
    // 高效的并发处理
    const [user, posts] = await Promise.all([
        fetchUser(),
        fetchUserPosts(userId)
    ]);

    如果其中一个 Promise 失败,

    Promise.all()
    会立即拒绝。如果需要所有 Promise 都解决(无论成功或失败),可以使用
    Promise.allSettled()

  3. 统一的错误处理: 使用
    try...catch
    块来包裹可能抛出错误的
    await
    表达式,或者整个
    async
    函数体,以便集中处理错误。对于
    Promise.all()
    ,如果其中一个失败,整个
    Promise.all()
    都会失败,所以外部的
    try...catch
    同样有效。
    async function getUserData() {
        try {
            const [user, posts] = await Promise.all([
                fetchUser(),
                fetchUserPosts(userId)
            ]);
            console.log(user, posts);
        } catch (error) {
            console.error("获取用户数据失败:", error);
            // 可以进行错误恢复或向上抛出
        }
    }
  4. 顶层
    await
    或 IIFE:
    在模块的顶层直接使用
    await
    已经成为标准(ES2022),但在不支持的环境中,或者在函数内部,你需要确保
    await
    始终在一个
    async
    函数中。如果要在非
    async
    作用域中使用
    await
    ,可以将其包裹在一个立即执行的
    async
    函数表达式(IIFE)中。
    (async () => {
        const result = await someAsyncOperation();
        console.log(result);
    })();
  5. 在循环中考虑并发: 如果需要在循环中处理异步操作,并且这些操作可以并行,那么应该将它们收集起来,然后使用
    Promise.all()
    进行处理。
    const processPromises = items.map(item => processItem(item));
    const results = await Promise.all(processPromises);

async/await
与传统的 Promise 链式调用相比,优势与劣势体现在哪里?

从我个人的开发经验来看,

async/await
确实是 Promise 的一个巨大进步,它解决了 Promise 链式调用在某些场景下的痛点,但也并非万能,各有其适用场景。

优势:

  1. 代码可读性极高: 这是
    async/await
    最显著的优势。它让异步代码看起来和写起来都与同步代码无异,线性的流程使得逻辑一目了然。对于复杂的业务逻辑,尤其是涉及多个异步依赖的场景,
    async/await
    的代码会比层层嵌套的
    .then()
    链条更易于理解和维护。
  2. 错误处理更直观:
    try...catch
    机制可以直接用于捕获
    await
    抛出的错误,这与我们处理同步错误的习惯完全一致。相比之下,Promise 链需要通过
    .catch()
    方法来处理错误,如果链条过长或错误处理逻辑分散,可能会变得复杂。
  3. 调试体验更好: 在使用
    async/await
    时,调试器可以像跟踪同步代码一样,在
    await
    表达式处暂停,并逐步执行。而 Promise 链的调试则可能因为异步回调的特性,导致堆栈信息不那么直观,难以追踪问题源头。
  4. 减少样板代码:
    async/await
    减少了
    .then()
    .catch()
    等 Promise 方法的显式调用,使得代码更加简洁。

劣势:

  1. 必须在
    async
    函数中使用
    await
    这是
    await
    的一个硬性限制。你不能在普通的非
    async
    函数中直接使用
    await
    ,否则会报语法错误。虽然现在有了顶层
    await
    ,但在一些老旧环境或特定场景下,仍然需要额外包裹一层
    async
    函数,这可能略显繁琐。
  2. 潜在的过度串行化问题: 正如前面提到的,如果开发者不理解
    async/await
    的并发机制,可能会习惯性地将所有异步操作都用
    await
    串行化执行,从而失去了并发的优势,导致性能下降。这要求开发者对异步编程模型有更深入的理解。
  3. 对 Promise 的依赖:
    async/await
    只是 Promise 的语法糖,它的底层仍然是 Promise。这意味着如果你不理解 Promise 的基本概念(如状态、链式调用、错误冒泡等),那么
    async/await
    的一些行为可能仍然会让你感到困惑。它并没有简化 Promise 本身,只是简化了 Promise 的使用方式。
  4. 可能掩盖异步本质: 虽然“看起来像同步”是它的优点,但也可能让一些初学者误以为它真的让 JavaScript 变成了多线程或阻塞式语言,从而忽略了其非阻塞的异步本质。这在某些复杂场景下可能导致对性能或并发的错误判断。

总的来说,

async/await
是现代 JavaScript 异步编程的首选方案,它在可读性、可维护性和错误处理方面带来了巨大的改进。然而,它并非银弹,开发者仍需理解其背后的 Promise 机制,并结合实际场景选择合适的并发策略,才能真正发挥其最大效用。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

557

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

395

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

756

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

478

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

474

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1051

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

659

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

菜鸟裹裹入口以及教程汇总
菜鸟裹裹入口以及教程汇总

本专题整合了菜鸟裹裹入口地址及教程分享,阅读专题下面的文章了解更多详细内容。

0

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.4万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号