JavaScript异步编程全解|Event Loop、Ajax、Fetch、Promise、手写Promise、Generator、Thunk、async/await

理解 JavaScript 异步编程,关键不是会不会写 await,而是能不能说清楚事件循环、任务优先级和异步方案为什么一步步演进成今天这样。

JavaScript 是单线程非阻塞脚本语言,主线程同一时刻只能执行一段代码。为了避免耗时任务(网络请求、定时器、文件读写)阻塞页面渲染和代码执行,JS 诞生了完整的异步编程体系

前端异步编程演进路线:原生回调函数 → Thunk 函数 → Promise → Generator 生成器 → async/await 终极方案。本文一站式吃透所有核心知识点,包含原理、区别、实战、手写源码、面试考点,是前端异步面试全能手册。

一、异步前置认知:单线程与 Event Loop(事件循环)

1.1 为什么 JS 是单线程?

JS 最初为浏览器交互而生,核心操作是操作 DOM。如果多线程同时操作 DOM,会引发节点覆盖、冲突错乱问题,因此设计为主线程唯一

单线程痛点:耗时任务会阻塞主线程,因此必须依靠异步机制解决阻塞问题。

1.2 Event Loop 事件循环(异步核心机制)

Event Loop 是 JS 实现异步的底层机制,俗称事件轮询,负责调度所有异步任务,让单线程实现非阻塞执行。

任务分类(面试必考)

JS 异步任务分为宏任务(MacroTask)微任务(MicroTask),优先级严格区分:

  • 宏任务:script 整体代码、setTimeout、setInterval、Ajax、Fetch、DOM 事件

  • 微任务:Promise.then/catch/finally、async/await、queueMicrotask、MutationObserver

Event Loop 执行顺序

  1. 执行同步代码,遇到异步任务推入对应队列

  2. 同步代码执行完毕,清空所有微任务队列(微任务优先级高于宏任务)

  3. 执行一个宏任务

  4. 再次清空微任务队列,循环往复

核心口诀:同步优先、微任务优先、宏任务轮询。

二、原生异步:Ajax 与 Fetch

2.1 Ajax(传统网络异步)

Ajax 基于 XMLHttpRequest 实现,是早期浏览器异步网络请求方案,核心作用是无刷新更新页面数据

原生 Ajax 特点

  • 基于回调函数实现异步,极易产生回调地狱

  • API 繁琐、配置复杂、兼容性好

  • 不支持 Promise,无法链式调用

2.2 Fetch(现代原生网络请求)

Fetch 是 ES6 推出的新型网络请求 API,基于 Promise 实现,是 Ajax 的现代替代方案。

核心优缺点

  • 优点:语法简洁、支持 Promise 链式调用、原生异步、无需第三方库

  • 缺点:默认不携带 cookie、报错只识别网络错误(状态码4xx/5xx不会报错)、不支持取消请求(原生)

基础示例

fetch('https://xxx.com/api')
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.log(err))

三、异步痛点:回调地狱与 Thunk 函数

3.1 回调地狱(Callback Hell)

多层异步回调嵌套,代码横向无限延伸,可读性、可维护性极差,错误难以捕获。

// 回调地狱示例
getData1(res1 => {
  getData2(res1, res2 => {
    getData3(res2, res3 => {
      // 无限嵌套
    })
  })
})

3.2 Thunk 函数(异步预处理方案)

Thunk 函数是多参函数柯里化的异步改造,核心作用:将多参数异步函数,转化为只接收回调的单参数函数,为 Generator 异步迭代铺路。

基础 Thunk 转换

// 普通异步函数
function readFile(path, callback) {}

// Thunk 封装:只留回调参数
function fileThunk(path) {
  return function(callback) {
    readFile(path, callback)
  }
}

核心价值:统一异步函数格式,让 Generator 可以自动化执行异步流程,是 Promise 普及前的异步优化方案。

四、Promise(异步标准化核心)

Promise 是 ES6 异步编程标准,彻底解决回调地狱问题,统一异步语法,支持链式调用,是现代异步编程的基石。

4.1 Promise 三大状态(不可逆)

  • pending 等待态:初始状态,未成功、未失败

  • fulfilled 成功态:执行成功,状态凝固不可逆

  • rejected 失败态:执行失败,状态凝固不可逆

4.2 核心语法与链式调用

new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => resolve("请求成功"), 1000)
})
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log("无论成败都会执行"))

4.3 常用静态方法

  • Promise.all():所有 Promise 成功才成功,一个失败直接失败(并行执行)

  • Promise.race():竞速执行,最快结束的 Promise 结果作为最终结果

  • Promise.allSettled():等待所有任务结束,返回所有结果(无视成败)

  • Promise.resolve()/reject():快速创建已完成/已失败 Promise

4.4 手写简易 Promise(面试必刷源码)

完整实现状态管理、链式调用、异步回调,适配原生核心逻辑:

class MyPromise {
  // 初始化状态、值、回调队列
  constructor(executor) {
    this.state = "pending"
    this.value = undefined
    this.reason = undefined
    this.onFulfilledCb = []
    this.onRejectedCb = []

    // 成功处理函数
    const resolve = (value) => {
      if (this.state !== "pending") return
      this.state = "fulfilled"
      this.value = value
      // 执行所有成功回调
      this.onFulfilledCb.forEach(fn => fn())
    }

    // 失败处理函数
    const reject = (reason) => {
      if (this.state !== "pending") return
      this.state = "rejected"
      this.reason = reason
      // 执行所有失败回调
      this.onRejectedCb.forEach(fn => fn())
    }

    // 捕获执行器异常
    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  // 链式then方法
  then(onFulfilled, onRejected) {
    // 状态成功执行回调
    if (this.state === "fulfilled") {
      onFulfilled(this.value)
    }
    // 状态失败执行回调
    if (this.state === "rejected") {
      onRejected(this.reason)
    }
    // 异步任务,暂存回调
    if (this.state === "pending") {
      this.onFulfilledCb.push(() => onFulfilled(this.value))
      this.onRejectedCb.push(() => onRejected(this.reason))
    }
  }
}

五、Generator 生成器(异步迭代器)

Generator 是 ES6 推出的可暂停、可恢复的函数,通过 yield 关键字实现代码分段执行,专门用于优化异步流程。

5.1 核心特性

  • 函数声明带 \*,执行返回迭代器对象

  • yield 暂停代码执行,next\(\) 恢复执行

  • 可以同步化书写异步代码,解决 Promise 链式冗余

5.2 基础用法

function* asyncFlow() {
  yield "第一步异步"
  yield "第二步异步"
  return "执行完毕"
}

const iterator = asyncFlow()
console.log(iterator.next()) // {value: '第一步异步', done: false}
console.log(iterator.next()) // {value: '第二步异步', done: false}
console.log(iterator.next()) // {value: '执行完毕', done: true}

5.3 核心局限

Generator 仅能暂停代码,无法自动执行异步流程,需要手动调用 next(),必须搭配 Thunk 函数或 Promise 才能实现自动化异步流程,因此被后续 async/await 替代。

六、async/await(异步终极方案)

ES8 推出的 async/await 是 Generator + Promise 的语法糖,是目前前端异步编程最优、最常用的方案,实现了异步代码同步化书写

6.1 核心特性

  • async:修饰函数,函数返回值自动包装为 Promise

  • await:暂停代码执行,等待 Promise 执行完成,解析结果返回

  • 彻底告别回调地狱、链式调用,代码可读性极高

6.2 实战示例

// 异步请求同步化
async function getDate() {
  try {
    const res = await fetch("https://xxx.com/api")
    const data = await res.json()
    console.log(data)
  } catch (err) {
    // 统一捕获异步错误
    console.log(err)
  }
}
getDate()

6.3 关键注意点

  • await 只能在 async 函数内部使用

  • 默认串行执行,多个独立异步任务建议用 Promise.all 并行优化

  • 异步错误必须通过 try/catch 捕获

七、JS异步体系完整演进总结

  1. 原生回调:最基础,缺点是回调地狱、维护性差

  2. Thunk 函数:标准化异步函数格式,适配 Generator 自动化

  3. Promise:解决回调地狱,支持链式调用,统一异步标准

  4. Generator:分段执行代码,实现异步同步化雏形,需手动迭代

  5. async/await:语法糖终极方案,简洁优雅,生产首选

八、高频面试核心辨析

  • Event Loop 微任务宏任务优先级? 同步代码 > 微任务 > 宏任务,逐轮循环执行

  • Promise 状态特点? 状态一旦变更不可逆,只能从 pending 变为 fulfilled/rejected

  • Generator 和 async/await 区别? Generator 需手动迭代,async/await 自动执行,是前者的语法糖

  • Ajax 和 Fetch 区别? Ajax 基于回调,API老旧;Fetch 基于 Promise,语法简洁,无内置失败拦截

  • Thunk 函数作用? 统一异步函数入参格式,实现 Generator 异步流程自动执行

本文总结

  • 事件循环的本质是同步代码、微任务和宏任务之间的调度顺序,先把这一点搞清楚,很多异步题就不再靠死记硬背。
  • Ajax、Fetch、Thunk、Promise、Generator 和 async/await 不是并列知识点,而是一条逐步解决异步可读性和可维护性的演进路线。
  • 手写 Promise 的价值不在于面试套路,而在于真正理解状态流转、回调队列和链式调用为什么能成立。
GYSTACK 文章文末广告 硅云云服务器活动 适合个人项目、轻量建站和出海业务部署。
后浪云移动端信息流广告 后浪云主机服务 适合长期部署、独立站和海外机房需求。