async 和 await 详解
文章目录
- 一、为什么需要 async 和 await?
- 二、基本概念
- 1. 定义和目的
- 2. 核心特性
- 3. 执行流程
- 4. 错误处理
- 三、底层原理
- 1. async 函数的转换
- 2. await 的工作机制
- 3. Promise 的整合
- 4. 错误处理
- 5. 关键点总结
- 6. 伪代码实现
- 四、最佳实践与陷阱
- 五、总结
一、为什么需要 async 和 await?
在传统异步编程中,处理多个异步操作时容易出现“回调地狱”(Callback Hell),代码嵌套层级深,难以维护。Promise 提供了 .then() 链式调用,但依然需要回调函数。async/await 的引入让异步代码的流程更清晰,语法更简洁。
二、基本概念
1. 定义和目的
定义:async
和await
是 JavaScript 处理异步操作的语法糖,基于 Promise 实现。
目的: 让异步代码的书写和阅读更接近同步代码,解决"回调地狱" 和 Promise 链式调用冗长的问题。
// Promise 链式调用
fetchData().then((data) => process(data).catch((err) => console.error(err)))//async/await 书写
async function handleData() {try {const data = await fetchData()} catch (error) {console.error(error)}
}
2. 核心特性
async
函数:- 声明:通过
async function
定义。 - 返回值:始终返回 Promise。若返回非 Promise 值,会自动包装为
Promise.resolve(value)
; 若抛出错误,返回Promise.reject(error)
- 声明:通过
await
表达式:- 只能在
async
函数内部使用。 - 暂停当前函数执行,等待 Promise 完成(
resolved
或rejected
)。 - 若等待的值非 Promise,直接返回该值。
- 只能在
async function example() {const result = await 42 // 等价于 await Promise.resolve(42)return result // 会被包装为 Promise
}
3. 执行流程
- 单线程非阻塞:
await
暂停的是当前async
函数的执行,主线程会仍可处理其他任务(如 UI 渲染、定时器等) - 事件循环机制: 浏览器引擎在 Promise 完成后将
async
函数推入微任务队列,恢复执行。
console.log('Start')
async function foo() {console.log('Before await')await Promise.resolve() // 暂停当前函数执行console.log('After await')
}
foo()
console.log('End')// Start -> Before await -> End -> Ater await
4. 错误处理
try/catch
: 捕获await
表达式的同步错误 和 Promise 拒绝(reject
)- 链式
.catch()
: 在调用async
函数后使用.catch()
。
async function fetchData() {try {const response = await fetch('invalid-url')return response.json()} catch (err) {console.error('Fetch failed:', err)throw err // 继续抛出供外部处理}
}fetchData().catch(() => console.log('External handling'))
三、底层原理
1. async 函数的转换
生成器函数: async 函数本质上是生成器函数(Generator)的语法糖。生成器通过 yield 暂停执行,并通过 next() 方法恢复。Babel 等工具将 async 函数转换为生成器,例如:
async function foo() {await bar()
}function* foo() {yield bar()
}
自动执行器: 需要一个执行器(如 co
库)驱动生成器,自动处理 yield
的 Promise, 递归调用 next()
或 throw()
来恢复执行。
2. await 的工作机制
暂停与恢复: 遇到 await
时,引擎将 async 函数暂停,保存当前上下文(变量、执行位置)。背后的生成器通过 yield 暂停,执行器等待 Promise 完成。
微任务队列: 当 Promise 解决(resolve/reject)时,回调被放入微任务队列。当前调用栈清空后,事件循环优先处理微任务,恢复 async 函数执行。
3. Promise 的整合
返回值包装: async 函数始终返回 Promise。若函数返回非 Promise 值,引擎将其包装为 Promise.resolve(value);若抛出异常,返回 Promise.reject(error)。
隐式 Promise 链: 每个 await 生成一个 Promise,按顺序链接。执行器确保后续代码在 Promise 解决后执行。
4. 错误处理
try/catch 模拟: 执行器通过生成器的 throw() 方法将 Promise 的 rejection 转换为 async 函数内的异常,可被 try/catch 捕获。
// 执行器伪代码
try {const result = yield promisegen.next(result) // 正常回复} catch (err) {gen.throw(err) // 抛出错误
}
5. 关键点总结
- 生成器 + 执行器: async/await 通过生成器暂停/恢复,执行器自动处理异步逻辑。
- 微任务调度 :Promise 回调通过微任务队列及时恢复,减少延迟。
- 同步化编码 :以同步写法管理异步流程,避免回调地狱,提升可读性。
6. 伪代码实现
// 将生成器函数转换为返回 Promise 的异步函数(类似 async 函数)
// 将生成器函数转换为返回 Promise 的异步函数(类似 async 函数)
function asyncToGenerator(generatorFunc) {// 返回一个新的函数,该函数接受原生成器函数的参数return function (...args) {// 创建生成器实例(相当于执行生成器函数获取迭代器)const generator = generatorFunc.apply(this, args)// 返回一个 Promise,用于模拟 async 函数的返回值return new Promise((resolve, reject) => {// 定义递归执行的步骤函数// key: 'next' 或 'throw',arg: 传递给生成器的值或错误function step(key, arg) {let result // 存储生成器方法执行结果try {// 执行生成器的 next() 或 throw() 方法// 例如 generator.next(arg) 或 generator.throw(arg)result = generator[key](arg)} catch (err) {// 捕获同步错误,直接拒绝 Promisereturn reject(err)}// 解构生成器返回的迭代结果 { value: any, done: boolean }const { value, done } = resultif (done) {// 生成器执行完成(遇到 return),解析最终结果resolve(value)} else {// 将 yield 的值转换为 Promise(兼容非 Promise 值)Promise.resolve(value).then(// 成功时递归调用 next() 传递结果,继续执行生成器(val) => step('next', val),// 失败时递归调用 throw() 抛出错误,生成器内部可通过 try/catch 捕获(err) => step('throw', err))}}// 首次启动生成器,执行第一个 yieldstep('next')})}
}
- 闭包结构
- 返回的新函数保持与原生成器函数相同的参数签名,使得调用方式与 async 函数一致
return function (...args) { ... }
- 生成器初始化
- 通过 apply 保留原函数的 this 绑定,传入参数创建生成器实例
- 相当于执行
const gen = generatorFunc(arg1, arg2)
const generator = generatorFunc.apply(this, args)
- Promise 封装
- 外层包裹 Promise,实现 async 函数返回 Promise 的特性
return new Promise((resolve, reject) => { ... })
-
递归步骤函数
- key 控制生成器的行为:‘next’ 继续执行,‘throw’ 抛出错误
- arg 传递值给生成器(yield 返回值或错误对象)
function step(key, arg) { ... }
- 错误边界处理
- 捕获生成器方法(next/throw)执行时的同步错误
- 例如:生成器内部未捕获的 throw new Error
try { ... } catch (err) { reject(err) }
-
迭代结构解构
- value:当前 yield 表达式返回的值
- done:生成器是否执行完成(遇到 return 或执行完毕)
const { value, done } = result
- 完成状态处理
- 当生成器执行完成时,将最终结果传递给 Promise 的 resolve
if (done) {resolve(value)
}
-
异步值处理
- 统一将 value 转换为 Promise,兼容 yield 42 这类非 Promise 值
- 相当于 await 的隐式 Promise 包装行为
Promise.resolve(value).then(...)
-
递归驱动生成器
- 异步成功后调用 next(val) 恢复生成器,传递解决值
- 异步失败后调用 throw(err) 让生成器内部可捕获错误
;(val) => step('next', val), (err) => step('throw', err)
- 启动执行
- 初始化第一次执行,相当于启动生成器的第一个 yield
step('next')
-
与 async/await 的对应关系
代码行为 async/await等价操作 generatorFunc async function yieldsomeValue await somePromise step(“throw”) 异步错误被转换为 try/catch 可捕获 Promise.resolve() await 自动包装非 Promise 值的逻辑
四、最佳实践与陷阱
- 并行优化
// 低效:顺序执行
const a = await fetchA()
const b = await fetchB()// 高效:并行执行
const [a, b] = await Promise.all([fetchA(), fetchB()])
- 循环中的
await
// 顺序执行(可能低效)
for (const url of urls) {await fetch(url)
}// 并行执行
await Promise.all(urls.map((url) => fetch(url)))
- 避免在非 async 函数中使用 await:会导致语法错误。
五、总结
async/await
是 javascript 处理异步操作的语法糖,基于 Promise 实现,目的是为了让异步代码的书写和阅读更接近同步逻辑,提升可读性和可维护性。它的核心是:一是 async 函数,通过 async function 声明,这个函数会自动将返回值包装为 Promise;二是 await 表达式,它只能在 async 函数内部使用,await 会暂停当前函数的执行,等待其后的 Promise 完成后再继续。执行流程上, await 会释放主线程去处理其他任务,待 Promise 解决后通过事件循环恢复执行;错误处理需通过 try/catch 捕获,避免因未处理的 reject 导致静默失败。实践中需注意优化并行操作(如用 Promise.all 替代顺序 await)、避免循环中不必要的阻塞。底层原理上,主要是使用 Generator + 自动执行器实现,最终在异步场景中实现简洁且高效的代码。