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 执行顺序
-
执行同步代码,遇到异步任务推入对应队列
-
同步代码执行完毕,清空所有微任务队列(微任务优先级高于宏任务)
-
执行一个宏任务
-
再次清空微任务队列,循环往复
核心口诀:同步优先、微任务优先、宏任务轮询。
二、原生异步: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异步体系完整演进总结
-
原生回调:最基础,缺点是回调地狱、维护性差
-
Thunk 函数:标准化异步函数格式,适配 Generator 自动化
-
Promise:解决回调地狱,支持链式调用,统一异步标准
-
Generator:分段执行代码,实现异步同步化雏形,需手动迭代
-
async/await:语法糖终极方案,简洁优雅,生产首选
八、高频面试核心辨析
-
Event Loop 微任务宏任务优先级? 同步代码 > 微任务 > 宏任务,逐轮循环执行
-
Promise 状态特点? 状态一旦变更不可逆,只能从 pending 变为 fulfilled/rejected
-
Generator 和 async/await 区别? Generator 需手动迭代,async/await 自动执行,是前者的语法糖
-
Ajax 和 Fetch 区别? Ajax 基于回调,API老旧;Fetch 基于 Promise,语法简洁,无内置失败拦截
-
Thunk 函数作用? 统一异步函数入参格式,实现 Generator 异步流程自动执行