Promise 异步编程面试题全解析
一、核心要点速览
💡 核心考点
- 三种状态: Pending、Fulfilled、Rejected(状态不可逆)
- 链式调用: then/catch/finally 返回新 Promise
- 常用方法: all、race、allSettled、any
- 错误处理: 捕获异常、错误传播
二、Promise 基础
1. 三种状态
javascript
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true
if (success) {
resolve('成功结果')
} else {
reject('失败原因')
}
}, 1000)
})
// 状态转换:
// Pending → Fulfilled (resolve)
// Pending → Rejected (reject)
// ⚠️ 状态只能改变一次,不可逆2. 状态机图示
┌──────────────────────────────────────────────────────────┐
│ Promise 状态机 │
└──────────────────────────────────────────────────────────┘
状态转换图:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────┐
│ PENDING │
│ (等待中) │
└──────┬──────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ FULFILLED │ │ REJECTED │
│ (已成功) │ │ (已失败) │
│ ✓ │ │ ✗ │
└───────────────┘ └───────────────┘
特点:
✓ 状态只能改变一次 (不可逆)
✓ Pending → Fulfilled (resolve)
✓ Pending → Rejected (reject)
✗ Fulfilled/Rejected 不能互相转换
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━3. 基本使用
javascript
promise
.then(result => {
console.log('成功:', result)
return result
})
.catch(error => {
console.error('失败:', error)
throw error // 可以继续传播
})
.finally(() => {
console.log('总是执行')
})三、链式调用
1. then 的返回值
javascript
// then 总是返回新 Promise
Promise.resolve(1)
.then(x => x + 1) // 返回普通值 → 自动包装
.then(x => x * 2) // 2 * 2 = 4
.then(x => Promise.resolve(x + 1)) // 返回 Promise
.then(x => console.log(x)) // 5
// 链式调用本质
const p1 = Promise.resolve(1)
const p2 = p1.then(x => x + 1) // p2 是新 Promise
const p3 = p2.then(x => x * 2) // p3 是另一个新 Promise
// 每个 then 都创建新 Promise
// 实现同步调用异步结果2. 链式调用时序图
时间 → ─────────────────────────────────────────────────►
Promise 链执行流程:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
创建 Promise
│
▼
┌─────────────────┐
│ new Promise │
└────────┬────────┘
│
▼
┌─────────┐
│ pending │
└─────────┘
│
│ 1 秒后 resolve(10)
▼
┌─────────────┐
│ fulfilled │
│ value: 10 │
└─────────────┘
│
▼
.then(x => x * 2)
│
▼
┌─────────────┐
│ pending │
└─────────────┘
│
│ resolve(20)
▼
┌─────────────┐
│ fulfilled │
│ value: 20 │
└─────────────┘
│
▼
.then(x => x + 5)
│
▼
┌─────────────┐
│ fulfilled │
│ value: 25 │
└─────────────┘
链式调用优势:
✓ 避免回调地狱
✓ 错误统一处理
✓ 代码可读性强
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━3. 错误传播
javascript
// 错误会沿着链向下传播
Promise.resolve(1)
.then(x => {
throw new Error('错误 1')
})
.then(x => {
// 不会执行,跳过
})
.catch(err => {
console.error(err.message) // '错误 1'
return 100 // 返回新值,继续链
})
.then(x => {
console.log(x) // 100
throw new Error('错误 2')
})
.catch(err => {
console.error(err.message) // '错误 2'
})
// 最佳实践:链末尾加 catch
fetchData()
.then(process)
.then(save)
.catch(error => {
// 捕获整个链的错误
console.error('操作失败:', error)
})四、Promise 常用方法
1. Promise.all(全部成功)
javascript
// 所有 Promise 都成功才成功,一个失败就失败
Promise.all([p1, p2, p3])
.then(results => {
// results = [result1, result2, result3]
console.log('全部成功:', results)
})
.catch(error => {
// 任意一个失败就到这里
console.error('有一个失败:', error)
})
// 实际应用:并发请求
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
])
// 性能优化:并行执行
// 总耗时 = max(p1, p2, p3),而非 sum2. Promise.all 执行流程图
┌──────────────────────────────────────────────────────────┐
│ Promise.all 执行流程 │
└──────────────────────────────────────────────────────────┘
成功场景:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
并发发起请求
│
├────► p1 请求 ────┐
│ │
├────► p2 请求 ────┼─► 陆续完成
│ │
└────► p3 请求 ────┘
│
▼
┌───────────┐
│ 全部完成 │
│ ✓ ✓ ✓ │
└───────────┘
│
▼
.then([
userRes,
postsRes,
commentsRes
])
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
失败场景 (假设 p2 失败):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
时间 → ─────────────────────────────────────────►
├────► p1 请求 ────► ✓
│
├────► p2 请求 ────► ✗ Error!
│ │
│ ▼
│ 立即触发 catch
│
└────► p3 请求 ────► ? (仍会完成但被忽略)
特点:
✓ 短路机制:一个失败,整体失败
✓ 结果顺序:与 Promise 顺序一致(非完成顺序)
✓ 适用场景:依赖多个结果的场景
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━3. Promise.race(竞赛)
javascript
// 第一个完成就完成(无论成功失败)
Promise.race([p1, p2, p3])
.then(firstResult => {
console.log('第一个完成:', firstResult)
})
.catch(firstError => {
console.error('第一个失败:', firstError)
})
// 应用:超时控制
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url)
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`请求超时 (${timeout}ms)`))
}, timeout)
})
return Promise.race([fetchPromise, timeoutPromise])
}
// 使用
fetchWithTimeout('/api/data', 3000)
.then(res => res.json())
.catch(err => console.error(err))4. Promise.allSettled(等待所有完成)
javascript
// 等待所有 Promise 完成,不关心成败
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value)
} else {
console.error('失败:', result.reason)
}
})
})
// 应用:需要知道每个请求的结果
const results = await Promise.allSettled([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
])
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`请求${index}成功:`, result.value)
} else {
console.error(`请求${index}失败:`, result.reason)
}
})5. Promise.any(任意成功)
javascript
// 第一个成功就成功,都失败才失败
Promise.any([p1, p2, p3])
.then(firstSuccess => {
console.log('第一个成功的:', firstSuccess)
})
.catch(errors => {
// AggregateError: 所有错误
console.error('都失败了:', errors.errors)
})
// 应用:多 CDN 备份
const urls = [
'https://cdn1.example.com/file.js',
'https://cdn2.example.com/file.js',
'https://cdn3.example.com/file.js'
]
const scripts = urls.map(url =>
new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.onload = () => resolve(script)
script.onerror = () => reject(new Error(`${url} 加载失败`))
document.head.appendChild(script)
})
)
try {
const firstLoaded = await Promise.any(scripts)
console.log('加载成功:', firstLoaded)
} catch (error) {
console.error('所有 CDN 都失败了')
}五、手写 Promise
1. 简化版实现
javascript
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value
onRejected = typeof onRejected === 'function'
? onRejected
: reason => { throw reason }
return new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (error) {
reject(error)
}
} else if (this.state === 'rejected') {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
} else {
// pending
this.onFulfilledCallbacks.push(() => {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (error) {
reject(error)
}
})
this.onRejectedCallbacks.push(() => {
try {
const x = onRejected(this.reason)
resolve(x)
} catch (error) {
reject(error)
}
})
}
})
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(callback) {
return this.then(
value => Promise.resolve(callback()).then(() => value),
reason => Promise.resolve(callback()).then(() => { throw reason })
)
}
}
// 静态方法
MyPromise.resolve = function(value) {
return new MyPromise(resolve => resolve(value))
}
MyPromise.reject = function(reason) {
return new MyPromise((_, reject) => reject(reason))
}
MyPromise.all = function(promises) {
return new MyPromise((resolve, reject) => {
const results = []
let completed = 0
promises.forEach((p, i) => {
p.then(value => {
results[i] = value
completed++
if (completed === promises.length) {
resolve(results)
}
}, reject)
})
})
}六、面试标准回答
Promise 是 ES6 引入的异步编程解决方案,有三种状态:Pending(等待中)、Fulfilled(已成功)、Rejected(已失败)。
核心特点:
- 状态只能改变一次,不可逆
- then/catch/always 返回新 Promise,支持链式调用
- 可以聚合多个 Promise(all、race 等)
解决了回调地狱问题:
javascript// 回调地狱 getData(a => { processData(a, b => { saveData(b, c => { console.log('完成') }) }) }) // Promise 链式 getData() .then(processData) .then(saveData) .then(() => console.log('完成'))常用方法:
- all: 所有都成功才成功,一个失败就失败(短路)
- race: 第一个完成就完成(竞赛)
- allSettled: 等待所有完成,返回各自结果
- any: 第一个成功就成功,都失败才失败
错误处理通过 catch 实现,错误会沿着链向下传播,直到被捕获。最佳实践是在链末尾添加统一的错误处理。
实际项目中,我经常使用 Promise.all 来并发请求多个接口,使用 race 来实现超时控制,使用 allSettled 来处理需要知道每个请求结果的场景。
七、记忆口诀
Promise 歌诀:
Promise 有三种态,
pending fulfilled rejected。
状态只能变一次,
链式调用解千愁。
then 接成功 catch 错,
finally 总是被执行。
all 要全都成功,
race 比谁快第一!
allSettled 等全部,
any 要一个成功。
错误传播向下走,
链尾记得加 catch!八、推荐资源
九、总结一句话
- Promise: 状态机 + 链式调用 = 异步编程标准化 ⚡
- all/race: 聚合并发 + 灵活控制 = 并发请求利器 🚀
- 错误处理: catch 捕获 + 错误传播 = 健壮的异步代码 ✓