一、为什么需要 async 和 await?

在传统异步编程中,处理多个异步操作时容易出现“回调地狱”(Callback Hell),代码嵌套层级深,难以维护。Promise 提供了 .then() 链式调用,但依然需要回调函数。async/await 的引入让异步代码的流程更清晰,语法更简洁。

二、基本概念

1. 定义和目的

定义asyncawait是 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 完成(resolvedrejected)。
    • 若等待的值非 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 promise
  gen.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) {
          // 捕获同步错误,直接拒绝 Promise
          return reject(err)
        }

        // 解构生成器返回的迭代结果 { value: any, done: boolean }
        const { value, done } = result

        if (done) {
          // 生成器执行完成(遇到 return),解析最终结果
          resolve(value)
        } else {
          // 将 yield 的值转换为 Promise(兼容非 Promise 值)
          Promise.resolve(value).then(
            // 成功时递归调用 next() 传递结果,继续执行生成器
            (val) => step('next', val),
            // 失败时递归调用 throw() 抛出错误,生成器内部可通过 try/catch 捕获
            (err) => step('throw', err)
          )
        }
      }

      // 首次启动生成器,执行第一个 yield
      step('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 + 自动执行器实现,最终在异步场景中实现简洁且高效的代码。

Logo

一站式 AI 云服务平台

更多推荐