导读:本期聚焦于弦宿​创作的《ES6中async函数如何使用?async/await常见问题一文讲清楚》,敬请观看详情。为什么async函数配合await写异步代码像同步一样顺畅?它能解决Promise链式调用的哪些痛点?本文系统梳理ES6 async函数的核心知识:async函数的返回值本质、await的执行顺序与错误处理、并行任务的正确写法、循环中使用await的陷阱,以及常见报错的原因分析。文中还对比了async函数与Generator、Promise三者的关系,给出大量可直接运行的代码示例,帮你彻底搞懂异步编程的底层逻辑,避免踩坑。

异步编程一直是JavaScript中绕不开的话题。从回调函数到Promise,再到Generator,JavaScript的异步方案不断演进,而ES6之后引入的ES2017标准中的async函数(俗称ES6 async函数的延伸),可以说是目前最优雅的异步解决方案。它让你用同步的写法处理异步逻辑,代码可读性大幅提升。本文将从基本语法、执行原理、错误处理到常见误区,把async函数的知识点一次性讲透。

ES6中async函数如何使用?async/await常见问题一文讲清楚

一、async函数的基本语法与返回值本质

async函数是在普通函数前加上async关键字修饰的函数,它的核心特点是:无论函数体内return什么,返回值永远是一个Promise对象。这是理解async函数一切行为的基础。

具体规则是:如果return一个普通值,这个值会被Promise.resolve()包装成resolved状态的Promise;如果return一个Promise,则直接返回这个Promise的状态;如果函数内部抛出异常,返回的Promise会变成rejected状态。看下面的例子:

async function foo() {
  return 1;
}
foo().then(val => console.log(val)); // 1

// 等价于
function bar() {
  return Promise.resolve(1);
}

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

很多初学者会误以为return await somePromise和直接return somePromise有区别。在绝大多数场景下两者效果相同,但在try/catch捕获错误的场景下,return await能把异步操作的异常留在当前函数的作用域内被catch捕获,而直接return则会让异常继续向上抛出。这是一个容易被忽略的细节。

二、await的执行顺序与错误处理机制

await后面通常跟一个Promise对象,它会让async函数暂停执行,等待Promise的状态确定后再继续往下执行。如果await的不是Promise,会先被Promise.resolve()转换。await返回的结果是Promise resolved时携带的值。

理解执行顺序的关键在于:async函数遇到await时会暂停,把控制权交还给调用者,也就是让出主线程,后续代码进入微任务队列。来看一个经典例子:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}
console.log('script start');
async1();
console.log('script end');
// 输出顺序:script start -> async1 start -> async2 -> script end -> async1 end

错误处理方面,如果await后面的Promise被reject,await表达式会抛出错误,整个async函数中断执行。因此务必要做好错误捕获,常用方式有两种:一种是外层包裹try/catch,另一种是给Promise本身链一个catch,让失败变成一个正常值继续处理:

// 方式一:try/catch
async function load() {
  try {
    const res = await fetch('/api/data');
    const data = await res.json();
    return data;
  } catch (e) {
    console.error('请求失败:', e);
    return null;
  }
}

// 方式二:await接catch,不中断后续逻辑
async function load2() {
  const res = await fetch('/api/data').catch(e => null);
  if (!res) return;
  return res.json();
}

三、常见误区与坑点解析

第一个高频误区是在循环中使用await。如果你在for循环里逐个await,多个异步任务会串行执行,总耗时等于每个任务耗时之和;而如果想在forEach里使用await,很遗憾这根本不生效,因为forEach的回调是普通函数,不会等待异步完成。正确的并行写法是配合Promise.all

// 串行写法,总耗时约3秒
async function serial() {
  for (const url of ['/a', '/b', '/c']) {
    await fetch(url);
  }
}

// 并行写法,总耗时约1秒
async function parallel() {
  const tasks = ['/a', '/b', '/c'].map(url => fetch(url));
  await Promise.all(tasks);
}

// forEach中使用await无效,不会等待
['/a', '/b'].forEach(async url => {
  await fetch(url); // forEach本身已返回,此处不会被等待
});

第二个常见问题是忘记await导致拿到的是Promise对象而不是值,例如写了const data = getData()而getData是async函数,那么data是一个Promise,调用data.xxx会得到undefined。遇到“属性为undefined”的报错时,优先检查是否漏写了await。

第三个坑是async函数中的顶层异常。由于async函数返回Promise,函数内部的同步错误也不会抛出到调用处的try/catch,而是走Promise的rejected通道。所以调用async函数时,要么用await配合try/catch,要么用.catch()兜底,否则会出现unhandled promise rejection警告。

四、async函数与Generator、Promise的关系

从语法上说,async函数可以看作Generator函数的语法糖。Generator用function*声明,配合yield和执行器(如co库)才能实现异步流程控制;而async函数把星号换成了async,把yield换成了await,并且内置了执行器,调用时自动逐步执行到底,不需要额外引入执行器。

对比Promise链式调用,async函数的优势在于消灭了层层嵌套的then回调,用线性的代码结构表达异步流程,错误处理也能统一用try/catch完成。三者可以这样理解:Promise是基础设施,Generator提供了可暂停函数的能力,async函数在两者之上提供了最接近同步思维的开发体验。

需要补充的是,await只能出现在async函数内部这条限制在较新的运行环境中已经放宽,顶级await可以在ES模块顶层直接使用,省去了包裹一层async函数的麻烦。不过在实际项目中,是否可用还要看目标运行环境的支持情况,老项目建议还是通过立即执行函数包裹的方式兼容。

掌握async函数的关键在于抓住两点:返回值永远是Promise,await会暂停并等待状态确定。把执行顺序和错误处理这两个核心机制吃透,日常开发中大部分异步问题都能迎刃而解。

异步编程一直是JavaScript中绕不开的话题。从回调函数到Promise,再到Generator,JavaScript的异步方案不断演进,而ES6之后引入的ES2017标准中的async函数(俗称ES6 async函数的延伸),可以说是目前最优雅的异步解决方案。它让你用同步的写法处理异步逻辑,代码可读性大幅提升。本文将从基本语法、执行原理、错误处理到常见误区,把async函数的知识点一次性讲透。

ES6中async函数如何使用?async/await常见问题一文讲清楚

一、async函数的基本语法与返回值本质

async函数是在普通函数前加上async关键字修饰的函数,它的核心特点是:无论函数体内return什么,返回值永远是一个Promise对象。这是理解async函数一切行为的基础。

具体规则是:如果return一个普通值,这个值会被Promise.resolve()包装成resolved状态的Promise;如果return一个Promise,则直接返回这个Promise的状态;如果函数内部抛出异常,返回的Promise会变成rejected状态。看下面的例子:

async function foo() {
  return 1;
}
foo().then(val => console.log(val)); // 1

// 等价于
function bar() {
  return Promise.resolve(1);
}

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

很多初学者会误以为return await somePromise和直接return somePromise有区别。在绝大多数场景下两者效果相同,但在try/catch捕获错误的场景下,return await能把异步操作的异常留在当前函数的作用域内被catch捕获,而直接return则会让异常继续向上抛出。这是一个容易被忽略的细节。

二、await的执行顺序与错误处理机制

await后面通常跟一个Promise对象,它会让async函数暂停执行,等待Promise的状态确定后再继续往下执行。如果await的不是Promise,会先被Promise.resolve()转换。await返回的结果是Promise resolved时携带的值。

理解执行顺序的关键在于:async函数遇到await时会暂停,把控制权交还给调用者,也就是让出主线程,后续代码进入微任务队列。来看一个经典例子:

async function async1() {
  console.log('async1 start');
  await async2();
  console.log('async1 end');
}
async function async2() {
  console.log('async2');
}
console.log('script start');
async1();
console.log('script end');
// 输出顺序:script start -> async1 start -> async2 -> script end -> async1 end

错误处理方面,如果await后面的Promise被reject,await表达式会抛出错误,整个async函数中断执行。因此务必要做好错误捕获,常用方式有两种:一种是外层包裹try/catch,另一种是给Promise本身链一个catch,让失败变成一个正常值继续处理:

// 方式一:try/catch
async function load() {
  try {
    const res = await fetch('/api/data');
    const data = await res.json();
    return data;
  } catch (e) {
    console.error('请求失败:', e);
    return null;
  }
}

// 方式二:await接catch,不中断后续逻辑
async function load2() {
  const res = await fetch('/api/data').catch(e => null);
  if (!res) return;
  return res.json();
}

三、常见误区与坑点解析

第一个高频误区是在循环中使用await。如果你在for循环里逐个await,多个异步任务会串行执行,总耗时等于每个任务耗时之和;而如果想在forEach里使用await,很遗憾这根本不生效,因为forEach的回调是普通函数,不会等待异步完成。正确的并行写法是配合Promise.all

// 串行写法,总耗时约3秒
async function serial() {
  for (const url of ['/a', '/b', '/c']) {
    await fetch(url);
  }
}

// 并行写法,总耗时约1秒
async function parallel() {
  const tasks = ['/a', '/b', '/c'].map(url => fetch(url));
  await Promise.all(tasks);
}

// forEach中使用await无效,不会等待
['/a', '/b'].forEach(async url => {
  await fetch(url); // forEach本身已返回,此处不会被等待
});

第二个常见问题是忘记await导致拿到的是Promise对象而不是值,例如写了const data = getData()而getData是async函数,那么data是一个Promise,调用data.xxx会得到undefined。遇到“属性为undefined”的报错时,优先检查是否漏写了await。

第三个坑是async函数中的顶层异常。由于async函数返回Promise,函数内部的同步错误也不会抛出到调用处的try/catch,而是走Promise的rejected通道。所以调用async函数时,要么用await配合try/catch,要么用.catch()兜底,否则会出现unhandled promise rejection警告。

四、async函数与Generator、Promise的关系

从语法上说,async函数可以看作Generator函数的语法糖。Generator用function*声明,配合yield和执行器(如co库)才能实现异步流程控制;而async函数把星号换成了async,把yield换成了await,并且内置了执行器,调用时自动逐步执行到底,不需要额外引入执行器。

对比Promise链式调用,async函数的优势在于消灭了层层嵌套的then回调,用线性的代码结构表达异步流程,错误处理也能统一用try/catch完成。三者可以这样理解:Promise是基础设施,Generator提供了可暂停函数的能力,async函数在两者之上提供了最接近同步思维的开发体验。

需要补充的是,await只能出现在async函数内部这条限制在较新的运行环境中已经放宽,顶级await可以在ES模块顶层直接使用,省去了包裹一层async函数的麻烦。不过在实际项目中,是否可用还要看目标运行环境的支持情况,老项目建议还是通过立即执行函数包裹的方式兼容。

掌握async函数的关键在于抓住两点:返回值永远是Promise,await会暂停并等待状态确定。把执行顺序和错误处理这两个核心机制吃透,日常开发中大部分异步问题都能迎刃而解。

async函数ES6await修改时间:2026-09-02 04:34:34

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