导读:本期聚焦于老毕创作的《javascript中async/await是什么?一文搞懂它们的工作原理与使用方法》,敬请观看详情。回调地狱曾经让无数前端代码变得难以维护,而async和await的出现彻底改变了JavaScript异步编程的写法。这篇文章将从Promise的演进讲起,详细解释async函数和await表达式的底层机制,包括async函数为什么总是返回Promise、await到底在等待什么、事件循环中微任务的执行顺序,以及错误处理的几种正确姿势。文中还给出对比回调、Promise和async/await三种方案的完整代码示例,帮助你写出看起来像同步代码一样清晰的异步逻辑,同时避免一些常见的使用误区。

异步编程是JavaScript绕不开的话题。无论是请求接口、读取文件还是设置定时器,都离不开异步操作。在回调函数时代,嵌套层次一多代码就变成了难以阅读的“回调地狱”,后来Promise用链式调用缓解了这个问题,但真正让异步代码写起来像同步代码一样的,是ES2017引入的async和await。本文将系统讲解async/await是什么、它们在底层是如何工作的,以及实际使用中的技巧和坑点。

javascript中async/await是什么?一文搞懂它们的工作原理与使用方法

一、async/await到底是什么

async是一个用来修饰函数的关键字,被它修饰的函数称为async函数,例如async function getData() {}await则只能出现在async函数内部(或在模块顶层,即顶层await),它后面通常跟一个Promise对象,表示“等待这个Promise出结果后再继续往下执行”。

先明确一点:async/await并不是全新的异步机制,它完全是建立在Promise之上的语法糖。也就是说,浏览器引擎在背后仍然是靠Promise和事件循环来驱动异步流程的,async/await只是让开发者可以用一种更接近同步代码的风格来组织异步逻辑。

看一个最简单的例子,体会一下两种写法的差异:

// Promise 链式写法
function getUser() {
  return fetch('/api/user')
    .then(res => res.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));
}

// async/await 写法
async function getUser() {
  try {
    const res = await fetch('/api/user');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

可以明显看出,async/await版本没有.then和.catch的链式嵌套,逻辑从上往下线性展开,可读性大幅提升。尤其是当多个步骤存在依赖关系时,这种优势更加明显。

二、async函数的返回值机制

很多初学者容易忽略一个关键点:async函数的返回值永远是一个Promise对象。无论你在函数体内return什么,外部拿到的都是Promise。return一个普通值,这个值会被自动包装成已 fulfilled 状态的Promise;return一个Promise,则外部直接等待这个Promise的结果。

async function foo() {
  return 1;
}
foo().then(v => console.log(v)); // 1,等价于 return Promise.resolve(1)

async function bar() {
  return Promise.resolve(2);
}
bar().then(v => console.log(v)); // 2

同样地,如果async函数内部抛出了异常,返回的Promise会进入rejected状态,异常对象会作为拒绝原因传递出去:

async function fail() {
  throw new Error('出错了');
}
fail().catch(e => console.log(e.message)); // 出错了

// 等价于
function fail2() {
  return Promise.reject(new Error('出错了'));
}

理解了这一点,就能明白为什么async函数可以互相组合调用:外层async函数可以直接await另一个async函数,因为后者返回的本身就是Promise,整个异步调用链天然是衔接起来的。

三、await到底在等什么,执行顺序是怎样的

await后面的表达式会先被求值,然后交给await处理。如果求值结果不是Promise(比如一个数字或字符串),它会被包装成已完成的Promise并立即继续执行;如果是一个Promise,函数就会在这里“暂停”,把控制权交还给调用者,等到Promise状态落定后再恢复执行后面的代码。

这里的“暂停”并不是阻塞线程,而是通过事件循环的微任务机制实现的。当await的Promise落定后,await之后的代码会被放入微任务队列,等当前执行栈清空后才执行。我们通过一个经典例子验证:

async function test() {
  console.log('1 - async函数开始');
  await Promise.resolve();
  console.log('4 - await之后的代码(微任务)');
}

console.log('0 - 脚本开始');
test();
console.log('2 - 调用test之后');
setTimeout(() => console.log('5 - 宏任务'), 0);
Promise.resolve().then(() => console.log('3 - 另一个微任务'));

// 输出顺序:0, 1, 2, 3, 4, 5

输出结果表明:test()被调用时同步执行到await那一行为止,然后立刻返回,继续执行外部的同步代码;await之后的代码进入微任务队列排队。掌握这个执行顺序对排查异步bug非常重要,比如为什么console.log打印的顺序和代码书写顺序不一致,往往就是微任务在起作用。

另外要注意,await会串行等待。如果多个异步操作之间没有依赖关系,逐个await会造成不必要的等待,这时候应该配合Promise.all并行执行:

// 串行写法:总耗时约 2 秒
async function serial() {
  const a = await delay(1000);
  const b = await delay(1000);
}

// 并行写法:总耗时约 1 秒
async function parallel() {
  const [a, b] = await Promise.all([delay(1000), delay(1000)]);
}

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

四、错误处理的正确姿势

await表达式遇到rejected的Promise时,会在当前位置抛出异常,因此标准的处理方式是try/catch包裹:

async function loadUser() {
  try {
    const res = await fetch('/api/user');
    if (!res.ok) {
      throw new Error('HTTP 状态码异常: ' + res.status);
    }
    return await res.json();
  } catch (err) {
    console.error('加载失败:', err);
    return null;
  }
}

除了try/catch,也可以在await后面直接调用.catch(),或者在调用async函数的地方用.catch()捕获,写法上更接近Promise风格,适合给单个异步操作兜底的场景。

还有一个常见的坑:在循环中await要谨慎。for...of循环里await会逐次等待,这是符合预期的;但如果把async函数传给forEach,await是不会被等待的,因为forEach本身不认识Promise,回调里的异步操作会“失控”。需要并发时应该使用Promise.all配合map

// 错误示范:forEach 不会等待 async 回调
[1, 2, 3].forEach(async id => {
  await fetchUser(id); // 不会按顺序等待
});

// 正确的串行方式
for (const id of [1, 2, 3]) {
  await fetchUser(id);
}

// 正确的并发方式
await Promise.all([1, 2, 3].map(id => fetchUser(id)));

五、总结与实践建议

async/await的本质是Promise的语法糖:async函数保证返回Promise,await负责暂停和恢复函数执行,二者配合让异步代码拥有同步般的阅读体验。使用时记住几条原则:一是错误处理不要遗漏,每个可能失败的await都要有兜底;二是无依赖的异步操作尽量用Promise.all并行化;三是理解微任务执行顺序,避免调试时对输出顺序产生困惑。

在现代JavaScript开发中,无论是浏览器端的fetch请求,还是Node.js中读写文件、查询数据库,async/await都已经成为事实上的标准写法。熟练掌握它的原理与细节,不仅能写出更简洁的代码,也能在面对复杂异步流程时做到心中有数。

async/awaitJavaScript异步Promise修改时间:2026-08-31 03:16:41

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。