【AJAX-Day2】Promise与回调地狱
🎯 核心目标:彻底理解 Promise 三种状态、链式调用、async/await、以及如何解决回调地狱
一、回调地狱(Callback Hell)
1.1 问题的产生
当多个异步操作有依赖关系时(必须等上一个完成才能执行下一个),就会产生层层嵌套的回调:
// 场景:登录 → 获取用户信息 → 获取用户的订单 → 获取订单详情
// 每一步都依赖上一步的结果
// 原生 XHR 回调地狱版本
login({ username, password }, function(userData) {
getUserInfo(userData.id, function(userInfo) {
getOrders(userInfo.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
// 终于拿到了……但代码已经缩进到了宇宙深处
console.log(detail)
// 更深的嵌套...
}, function(err) { console.error(err) })
}, function(err) { console.error(err) })
}, function(err) { console.error(err) })
}, function(err) { console.error(err) })
回调 地狱的问题:
- 代码横向扩展,难以阅读
- 错误处理分散,难以维护
- 代码逻辑难以复用
- 无法使用 try/catch
1.2 Promise 的诞生
Promise 是 ES6 引入的异步编程 解决方案,它将异步操作封装成一个对象,通过链式调用解决回调地狱。
二、Promise 详解
2.1 Promise 的三种状态
pending(等待中)
↓
成功 → fulfilled(已完成)→ 执行 .then() 的回调
失败 → rejected(已拒绝)→ 执行 .catch() 的回调
⚠️ 状态一旦改变,就不会再变(不可逆)
// 创建 Promise
const p = new Promise((resolve, reject) => {
// 执行异步操作
setTimeout(() => {
const success = Math.random() > 0.5
if (success) {
resolve('成功的数据') // 状态:pending → fulfilled
} else {
reject(new Error('失败的原因')) // 状态:pending → rejected
}
}, 1000)
})
// 消费 Promise
p.then(data => {
console.log('成功:', data) // 'success的数据'
}).catch(err => {
console.error('失败:', err.message)
})
2.2 Promise 链式调用
then() 返回一个新的 Promise,这是链式调用的关键。
// 链式调用解决回调地狱
axios.get('/api/login', { params: { username, password } })
.then(({ data }) => {
// 第一步成功,返回值会传给下一个 then
return axios.get(`/api/users/${data.userId}`)
})
.then(({ data }) => {
// 第二步成功
return axios.get(`/api/orders?userId=${data.id}`)
})
.then(({ data }) => {
// 第三步成功
console.log('订单列表:', data)
})
.catch(err => {
// 任意一步失败都会被这里捕获
console.error('出错了:', err.message)
})
then() 的返回值规则:
promise
.then(data => {
return 123 // 返回普通值 → 下一个 then 收到 123
})
.then(data => {
return axios.get('/api/xxx') // 返回 Promise → 等待该 Promise 完成
})
.then(data => {
// data 是上面 axios.get 的结果
throw new Error('主动抛出错误') // 抛出错误 → 跳转到 catch
})
.catch(err => { ... })
2.3 Promise 静态方法
// Promise.resolve():创建一个立即成功的 Promise
const p1 = Promise.resolve(42)
p1.then(v => console.log(v)) // 42
// Promise.reject():创建一个立即失败的 Promise
const p2 = Promise.reject(new Error('失败'))
p2.catch(e => console.error(e))
// Promise.all():所有都成功才成功,有一个失败就失败
Promise.all([
axios.get('/api/users'),
axios.get('/api/posts'),
axios.get('/api/comments')
]).then(([users, posts, comments]) => {
// 三个请求都完成后才执行
console.log(users.data, posts.data, comments.data)
}).catch(err => {
// 任意一个失败则触发
console.error(err)
})
// Promise.allSettled():所有都完成(无论成功失败)才结束(ES2020)
Promise.allSettled([
axios.get('/api/users'),
axios.get('/api/will-fail')
]).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value)
} else {
console.log('失败:', result.reason)
}
})
})
// Promise.race():第一个完成的(无论成功失败)决定结果
// 应用:超时控制
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), ms)
)
return Promise.race([promise, timeout])
}
// Promise.any():第一个成功的决定结果(ES2021)
Promise.any([
fetch('https://api1.example.com'), // 可能失败
fetch('https://api2.example.com'), // 备用地址
]).then(response => {
// 用最先成功的那个
})
三、async / await
3.1 什么是 async/await?
async/await 是 ES2017 引入的语法糖,让异步代码写起来像同步代码一样直观。
// Promise 链式调用版
function getData() {
return axios.get('/api/step1')
.then(r1 => axios.get(`/api/step2/${r1.data.id}`))
.then(r2 => axios.get(`/api/step3/${r2.data.id}`))
.then(r3 => r3.data)
.catch(err => console.error(err))
}
// async/await 版(更清晰!)
async function getData() {
try {
const r1 = await axios.get('/api/step1')
const r2 = await axios.get(`/api/step2/${r1.data.id}`)
const r3 = await axios.get(`/api/step3/${r2.data.id}`)
return r3.data
} catch (err) {
console.error(err)
}
}
3.2 async 函数的特点
// async 函数总是返回一个 Promise
async function fn() {
return 42 // 等价于 return Promise.resolve(42)
}
fn().then(v => console.log(v)) // 42
// await 只能在 async 函数内使用
async function example() {
// await 等待 Promise resolve,拿到值
const result = await Promise.resolve('hello')
console.log(result) // 'hello'
// await 暂停当前 async 函数,但不阻塞主线程!
const data = await axios.get('/api/data')
// data 就是 axios 响应对象(不是 Promise)
console.log(data.data)
}
3.3 错误处理
// 方式一:try/catch(推荐,清晰)
async function fetchUser(id) {
try {
const { data } = await axios.get(`/api/users/${id}`)
return data
} catch (error) {
console.error('获取用户失败:', error.response?.data?.message)
return null
}
}
// 方式二:.catch() 链(适合单个请求)
async function fetchUser(id) {
const { data } = await axios.get(`/api/users/${id}`)
.catch(err => {
console.error(err)
return { data: null }
})
return data
}
// 方式三:封装 await 的错误处理(进阶)
async function to(promise) {
try {
const result = await promise
return [null, result]
} catch (error) {
return [error, null]
}
}
async function fetchUser(id) {
const [err, { data }] = await to(axios.get(`/api/users/${id}`))
if (err) {
console.error(err)
return null
}
return data
}
3.4 并发请求(避免串行等待)
// ❌ 串行(效率低,共等待 3 秒)
async function slowVersion() {
const r1 = await axios.get('/api/a') // 等 1 秒
const r2 = await axios.get('/api/b') // 等 1 秒
const r3 = await axios.get('/api/c') // 等 1 秒
return [r1.data, r2.data, r3.data]
}
// ✅ 并发(效率高,只等最长的那个)
async function fastVersion() {
const [r1, r2, r3] = await Promise.all([
axios.get('/api/a'),
axios.get('/api/b'),
axios.get('/api/c')
])
return [r1.data, r2.data, r3.data]
}
四、Promise 实现原理(简版)
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn(value))
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn(reason))
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
try {
const result = onFulfilled(this.value)
resolve(result)
} catch (e) { reject(e) }
}
if (this.state === 'rejected') {
try {
const result = onRejected(this.reason)
resolve(result)
} catch (e) { reject(e) }
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(v => {
try { resolve(onFulfilled(v)) } catch (e) { reject(e) }
})
this.onRejectedCallbacks.push(r => {
try { resolve(onRejected(r)) } catch (e) { reject(e) }
})
}
})
}
}
五、知识图谱
Promise 与回调地狱
├── 回调地狱
│ ├── 原因:异步依赖导致嵌套
│ └── 问题:可读性差、错误分散、难维护
├── Promise
│ ├── 三种状态:pending → fulfilled/rejected(不可逆)
│ ├── 创建:new Promise((resolve, reject) => {...})
│ ├── 消费:.then(成功) / .catch(失败) / .finally(都执行)
│ ├── 链式:then 返回新 Promise,可串联
│ └── 静态方法
│ ├── all:全部成功才成功
│ ├── allSettled:全部完成(不管成败)
│ ├── race:第一个完成的决定结果
│ └── any:第一个成功的决定结果
└── async/await
├── async 函数返回 Promise
├── await 等待 Promise,暂停当前函数(不阻塞线程)
├── 错误处理:try/catch
└── 并发:Promise.all 并行,避免串行等待
六、高频面试题
Q1:Promise 的三种状态是什么?状态能否改变?
pending(等待)、fulfilled(成功)、rejected(失败)。状态一旦从 pending 变为 fulfilled 或 rejected,就不可再改变(不可逆)。
Q2:async/await 和 Promise 的关系?
async/await 是 Promise 的语法糖。async 函数返回一个 Promise;await 只是
.then()的更优雅写法,它暂停当前 async 函数执行,等待 Promise resolve 后继续,但不阻塞 JS 线程(事件循环继续运行)。
Q3:Promise.all 和 Promise.allSettled 的区别?
Promise.all:只要有一个 rejected 就立即失败,适合所有请求都必须成功的场景;Promise.allSettled:等所有 Promise 都完成(无论成败),返回每个的状态和值/原因,适合需要知道所有结果的场景。
Q4:以下代码的输出顺序?
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')
// 输出:1 4 3 2
// 同步先执行(1,4),微任务优先(3),最后宏任务(2)
⬅️ 上一篇:Day1 - HTTP协议与XHR基础 ➡️ 下一篇:Day3 - Axios深入与请求拦截