JavaScript异步编程之Async/Await用法完全指南

 更新时间:2026年01月30日 09:54:52   作者:努力进步中的小白  
async/await是JavaScript中基于Promise的异步语法糖,使异步代码更直观易读,这篇文章主要介绍了JavaScript异步编程之Async/Await用法完全指南的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

一、async/await 的发展史:为什么会出现?

要理解 async/await,得先回顾它的 “前辈” 们 ——async/await 不是凭空出现的,而是为了解决 Promise 的 “小痛点”,是 JS 异步编程的终极语法糖。

1. 异步编程的 “进化史”(铺垫)

JS 异步编程的发展,核心是 “让异步代码更像同步代码”,一步步解决 “写起来别扭” 的问题:

  • 阶段 1:回调函数(ES5 及以前):解决了异步执行的问题,但多层嵌套形成 “回调地狱”,代码臃肿;
  • 阶段 2:Promise(ES2015/ES6,2015 年):解决了回调地狱,把嵌套改成链式调用,但链式调用多了(比如 5-6 个异步操作),then依然会串联成 “长链条”,逻辑不够直观;
  • 阶段 3:Generator 函数(ES2015/ES6,过渡方案):可以暂停 / 恢复函数执行,能模拟 “同步写异步”,但语法繁琐(需要手动调用next()),还需要第三方库(如 co)封装,普通人难用;
  • 阶段 4:async/await(ES2017/ES8,2017 年):基于 PromiseGenerator,是官方推出的 “语法糖”—— 既保留了 Promise 的异步特性,又能**像写同步代码一样写异步逻辑**,成为现在异步编程的主流。

2. 各阶段具体代码示例

阶段一:回调函数时代(Callback Hell)

getData(function(data1) {
    process(data1, function(data2) {
        save(data2, function(data3) {
            display(data3, function() {
                // 更多嵌套...
                console.log("完成了,但代码已经看不懂了!");
            });
        });
    });
});

问题:代码横向发展,形成"金字塔",难以维护和调试。

阶段二:Promise时代(ES6,2015年)

getData()
    .then(process)
    .then(save)
    .then(display)
    .then(() => console.log("完成"))
    .catch(error => console.error("出错:", error));

改进:链式调用,纵向发展,错误处理统一。

阶段三:Generator函数时代(过渡方案)

// 2015年:Generator + Promise
function* asyncGenerator() {
    const data1 = yield getData();
    const data2 = yield process(data1);
    const data3 = yield save(data2);
    yield display(data3);
    console.log("完成");
}

// 需要外部执行器
function run(generator) {
    const iterator = generator();
    function handle(result) {
        if (result.done) return;
        result.value.then(data => {
            handle(iterator.next(data));
        });
    }
    handle(iterator.next());
}

问题:需要外部执行器,语法复杂,不直观。

阶段四:Async/Await时代(ES8,2017年)

// 2017年:Async/Await
async function handleData() {
    try {
        const data1 = await getData();
        const data2 = await process(data1);
        const data3 = await save(data2);
        await display(data3);
        console.log("完成");
    } catch (error) {
        console.error("出错:", error);
    }
}

革命性改进:代码看起来像同步代码,但实际上是异步执行!

3. 核心本质

async/await 不是替代 Promise,而是基于 Promise 的语法糖—— 所有 async/await 能实现的功能,用 Promise 都能实现,只是 async/await 写起来更简单、更易读。

二、async/await 的核心用法

1. 基本语法:async + await

(1)async:声明 “异步函数”

  • async修饰的函数,会变成 “异步函数”
  • 异步函数的返回值会自动包装成 Promise(哪怕你返回的是普通值)。
// 在函数前加上 async,这个函数就变成了异步函数
async function normalFunction() {
    return "Hello Async";  // 自动包装成Promise
}

// 等价于:
function normalFunction() {
    return Promise.resolve("Hello Async");
}

// 使用
normalFunction().then(console.log);  // "Hello Async"

(2)await:等待 Promise 完成

  • await只能用在async 函数内部(这是新手最容易踩的坑);
  • await后面必须跟一个Promise 对象(如果不是,会直接返回该值);
  • await会 “暂停” 函数执行,直到 Promise 完成(成功 / 失败),再继续执行后面的代码 —— 但这个 “暂停” 是非阻塞的(不会卡主线程)。
async function fetchUser() {
    // await 会暂停函数执行,等待Promise完成
    const response = await fetch('https://api.example.com/user');
    const user = await response.json();
    return user;
}

// await 只能在 async 函数内部使用
// 下面的代码会报错:
// const data = await fetchUser();  // ❌ 错误

💡 对比 Promise:原来的.then链式调用,现在直接用await拿到结果,代码和同步逻辑完全一致!

(3)基本用法示例

// 模拟异步函数
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function example() {
    console.log("开始");
    
    await delay(1000);  // 等待1秒
    console.log("1秒后");
    
    await delay(2000);  // 再等待2秒
    console.log("再过2秒后");
    
    return "任务完成";
}

// 调用
example().then(result => console.log(result));
// 输出:
// 开始
// 1秒后
// 再过2秒后
// 任务完成

2. 错误处理:try/catch(核心)

Promise 用.catch处理错误,async/await 用try/catch捕获错误(因为 await 会 “解包” Promise 的失败状态,必须手动捕获)。

示例 1:捕获单个异步操作的错误

async function getScoreAsync() {
  try {
    // 成功:执行try里的代码,失败:跳转到catch
    const score = await getScore(); 
    console.log("分数:", score);
  } catch (error) {
    // 捕获Promise的reject错误
    console.log("出错了:", error);
  }
}

// 测试:如果getScore返回reject(比如分数50),会执行catch
getScoreAsync();

示例 2:捕获多个异步操作的错误

如果有多个 await一个 try/catch 可以捕获所有错误:

// 模拟两个异步请求
function getUser() {
  return new Promise((resolve) => setTimeout(() => resolve({ id: 1 }), 500));
}
function getCourse(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      userId === 1 ? resolve({ name: "语文" }) : reject("用户不存在");
    }, 500);
  });
}

// 用async/await调用,统一捕获错误
async function getInfo() {
  try {
    const user = await getUser(); // 第一个异步操作
    const course = await getCourse(user.id); // 第二个异步操作
    console.log("用户课程:", course); // 输出:{ name: "语文" }
  } catch (error) {
    console.log("错误:", error); // 任何一个异步失败,都会走到这里
  }
}

getInfo();

3. 实战案例:替代 Promise 链式调用(解决回调地狱)

回顾之前 “登录→查课程→查成绩” 的案例,用 async/await 改写后,代码更简洁:

// 封装3个异步请求(返回Promise)
function login() {
  return new Promise(resolve => setTimeout(() => resolve({ id: 1 }), 500));
}
function getCourse(userId) {
  return new Promise(resolve => setTimeout(() => resolve({ id: 10 }), 500));
}
function getScore(courseId) {
  return new Promise(resolve => setTimeout(() => resolve({ score: 90 }), 500));
}

// async/await版本:同步写法,异步执行
async function getStudentInfo() {
  try {
    const user = await login(); // 登录
    const course = await getCourse(user.id); // 查课程
    const score = await getScore(course.id); // 查成绩
    console.log("最终成绩:", score); // 输出:{ score: 90 }
  } catch (error) {
    console.log("出错了:", error);
  }
}

getStudentInfo();

对比 Promise 链式调用:没有任何.then,代码和同步逻辑完全一致,新手也能一眼看懂执行顺序。

4. 进阶用法:并发执行(避免不必要的等待)

新手容易犯的错:用 await 串行执行所有异步操作,导致耗时过长。比如:

// 错误示例:串行执行(总耗时=1s+1s=2s)
async function badDemo() {
  const res1 = await new Promise(resolve => setTimeout(() => resolve(1), 1000));
  const res2 = await new Promise(resolve => setTimeout(() => resolve(2), 1000));
  console.log(res1, res2); //  // 总时间 = 2个请求时间之和=2s
}

如果两个异步操作互不依赖(比如同时查两个独立的接口),应该用Promise.all实现并发:

// 正确示例:并发执行(总耗时=1s)
async function goodDemo() {
  // 1. 先创建两个Promise对象(不要先await)
  const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
  const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 1000));
  
  // 2. 用Promise.all并发执行,await等待所有完成
  const [res1, res2] = await Promise.all([promise1, promise2]);
  console.log(res1, res2); // 总耗时1秒
}

💡 核心原则:

  • 异步操作有依赖(比如查成绩依赖课程 ID):用 await 串行执行;
  • 异步操作无依赖:用Promise.all+await 并发执行,提升效率。

5. 循环中的 await:避免踩坑

如果在循环中用 await,默认是串行执行,比如:

// 串行执行:总耗时3秒(1+1+1)
async function loopDemo() {
  const list = [1, 2, 3];
  for (const item of list) {
    await new Promise(resolve => setTimeout(() => {
      console.log(item);
      resolve();
    }, 1000));
  }
}

注意forEach 中的 async/await 不会按预期工作(❌ 错误)

如果想让循环中的异步操作并发执行,先收集所有 Promise,再用Promise.all

//  map + Promise.all(并行) 并发执行:总耗时1秒
async function loopDemo() {
  const list = [1, 2, 3];
  // 1. 收集所有Promise
  const promises = list.map(item => 
    new Promise(resolve => setTimeout(() => {
      console.log(item);
      resolve();
    }, 1000))
  );
  // 2. 并发执行
  await Promise.all(promises);
}

三、async/await 的常见坑与注意事项

  1. await 只能在 async 函数中使用:如果在普通函数里用 await,会直接报错(比如在全局作用域、普通 for 循环回调里);
    解决:要么把函数改成 async,要么用 IIFE(立即执行函数)包裹:
// 全局作用域使用await(ES模块环境)
(async () => {
  const score = await getScore();
  console.log(score);
})();
  1. 不要滥用 await:所有异步操作都用 await 串行执行,会导致性能极低(比如 10 个独立接口,本可以 1 秒完成,结果用了 10 秒);
    解决无依赖的异步操作,用Promise.all并发执行。

  2. 错误捕获要完整:如果漏写 try/catch,await 的 Promise 失败时,会抛出 “未捕获的错误”,导致程序崩溃;
    解决:要么用 try/catch,要么在 await 后面加.catch:

    // 单个异步操作的简化错误捕获
    const score = await getScore().catch(error => console.log(error));
    

四、总结:核心要点回顾

发展史:async/await 是 ES2017(2017)纳入标准的语法糖,基于 Promise,解决了 Promise 链式调用的繁琐,让异步代码像同步代码;

核心语法:

  • async修饰函数,使其返回 Promise;
  • await只能在 async 函数内使用,等待 Promise 完成并返回结果;
  • 错误处理用try/catch(替代 Promise 的.catch)。

使用原则:

  • 有依赖的异步操作:await 串行执行;
  • 无依赖的异步操作:Promise.all+await 并发执行;
  • 必加错误捕获,避免程序崩溃。

async/await 是目前 JS 异步编程的 “最优解”,掌握它后,不管是前端调接口、Node.js 写后端,处理异步逻辑都会变得极其简单~

到此这篇关于JavaScript异步编程之Async/Await用法的文章就介绍到这了,更多相关JS Async/Await用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解微信小程序动画Animation执行过程

    详解微信小程序动画Animation执行过程

    这篇文章主要介绍了微信小程序动画Animation执行过程 / 实现过程 / 实现方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • Layui组件Table绑定行点击事件和获取行数据的方法

    Layui组件Table绑定行点击事件和获取行数据的方法

    今天小编就为大家分享一篇Layui组件Table绑定行点击事件和获取行数据的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • p5.js码绘“跳动的小正方形”的实现代码

    p5.js码绘“跳动的小正方形”的实现代码

    这篇文章主要介绍了p5.js码绘“跳动的小正方形”,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-10-10
  • Javascript设计模式之装饰者模式详解篇

    Javascript设计模式之装饰者模式详解篇

    本文主要介绍了Javascript设计模式之装饰者模式的相关知识。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • js下写一个事件队列操作函数

    js下写一个事件队列操作函数

    异步操作可能会产生你不希望的事件触发顺序。这个问题以前也遇到过,当时没想太多,也就是直接多层嵌套(在ajax返回以后嵌套下一个事件)来解决。
    2010-07-07
  • 利用JS判断鼠标移入元素的方向

    利用JS判断鼠标移入元素的方向

    本文对JS判断鼠标移入元素的方向的实现方法进行介绍,并分享了完整的示例代码,有需要的朋友可以看下
    2016-12-12
  • 解决js相同的正则多次调用test()返回的值却不同的问题

    解决js相同的正则多次调用test()返回的值却不同的问题

    今天小编就为大家分享一篇解决js相同的正则多次调用test()返回的值却不同的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-10-10
  • JavaScript中setTimeout使用重要的注意事项总结

    JavaScript中setTimeout使用重要的注意事项总结

    setTimeout用于延迟执行函数,异步特性使其不阻塞代码,这篇文章主要介绍了JavaScript中setTimeout使用注意事项的相关资料,需注意作用域绑定、参数传递、取消定时器及精确度问题,需要的朋友可以参考下
    2025-05-05
  • BootStrap表单验证实例代码

    BootStrap表单验证实例代码

    这篇文章主要介绍了bootstrap表单验证的实例代码,代码中包括引入的js文件,具体实现方法,大家参考本文
    2017-01-01
  • 本人自用的global.js库源码分享

    本人自用的global.js库源码分享

    这篇文章主要介绍了本人自用的global.js库源码分享,源码中包含常用WEB操作,如命名空间、DOM操作、数据判断、Cookie操作等功能,需要的朋友可以参考下
    2015-02-02

最新评论