Promise & async/await
异步编程是前端开发的核心技能。从回调地狱到 Promise 链,再到 async/await,理解每一层抽象的底层原理。
回调地狱的问题
js
// ❌ 回调地狱:难以阅读、错误处理分散
getUser(userId, (err, user) => {
if (err) return handleError(err)
getOrders(user.id, (err, orders) => {
if (err) return handleError(err)
getOrderDetails(orders[0].id, (err, details) => {
if (err) return handleError(err)
render(user, orders, details)
})
})
})Promise 基础
js
// Promise 三种状态:pending → fulfilled / rejected
const p = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('成功') // 状态变为 fulfilled
} else {
reject(new Error('失败')) // 状态变为 rejected
}
}, 1000)
})
p.then(value => console.log(value))
.catch(err => console.error(err))
.finally(() => console.log('无论成功失败都执行'))Promise 链式调用
js
// ✅ Promise 链:扁平化异步流程
getUser(userId)
.then(user => getOrders(user.id)) // 返回新 Promise
.then(orders => getOrderDetails(orders[0].id))
.then(details => render(details))
.catch(err => handleError(err)) // 统一错误处理.then 的返回值规则:
js
Promise.resolve(1)
.then(v => v + 1) // 返回普通值 → 包装成 Promise.resolve(2)
.then(v => Promise.resolve(v * 2)) // 返回 Promise → 等待它
.then(v => { throw new Error('oops') }) // 抛出错误 → 变为 rejected
.catch(err => 'recovered') // catch 返回值 → 变为 fulfilled
.then(v => console.log(v)) // 'recovered'Promise 静态方法
js
// Promise.all — 全部成功才成功,一个失败就失败
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
])
// 并行请求,比串行快 3 倍
// Promise.allSettled — 等待所有完成,不管成功失败
const results = await Promise.allSettled([
fetchUser(1),
fetchUser(999), // 可能失败
fetchUser(2)
])
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value)
} else {
console.log('失败:', result.reason)
}
})
// Promise.race — 第一个完成的(成功或失败)
const result = await Promise.race([
fetch('/api/data'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), 5000)
)
])
// Promise.any — 第一个成功的(ES2021)
const fastest = await Promise.any([
fetch('https://mirror1.example.com/data'),
fetch('https://mirror2.example.com/data'),
fetch('https://mirror3.example.com/data')
])
// 返回最快成功的那个,全部失败才抛 AggregateErrorasync/await 深度解析
js
// async 函数总是返回 Promise
async function fetchData() {
return 42 // 等价于 return Promise.resolve(42)
}
// await 暂停当前 async 函数,等待 Promise 完成
async function loadUserData(userId) {
try {
const user = await fetchUser(userId) // 等待
const orders = await fetchOrders(user.id) // 等待
return { user, orders }
} catch (err) {
console.error('加载失败:', err)
throw err // 重新抛出,让调用者处理
}
}并行 vs 串行
js
// ❌ 串行(慢):总耗时 = 请求1 + 请求2
async function serial() {
const user = await fetchUser(1) // 等 500ms
const posts = await fetchPosts(1) // 再等 300ms
// 总计 800ms
}
// ✅ 并行(快):总耗时 = max(请求1, 请求2)
async function parallel() {
const [user, posts] = await Promise.all([
fetchUser(1), // 同时发起
fetchPosts(1) // 同时发起
])
// 总计 500ms
}
// ✅ 先启动,后等待
async function parallelV2() {
const userPromise = fetchUser(1) // 立即发起请求
const postsPromise = fetchPosts(1) // 立即发起请求
const user = await userPromise // 等待结果
const posts = await postsPromise // 等待结果
}循环中的异步
js
const ids = [1, 2, 3, 4, 5]
// ❌ forEach 不等待 async 回调
ids.forEach(async (id) => {
const user = await fetchUser(id) // forEach 不关心返回的 Promise
console.log(user)
})
// 顺序不确定,forEach 已经结束了
// ✅ 串行:for...of
for (const id of ids) {
const user = await fetchUser(id)
console.log(user) // 按顺序,一个接一个
}
// ✅ 并行:Promise.all + map
const users = await Promise.all(ids.map(id => fetchUser(id)))
// ✅ 并发控制:限制同时请求数量
async function fetchWithConcurrency(ids, limit = 3) {
const results = []
for (let i = 0; i < ids.length; i += limit) {
const chunk = ids.slice(i, i + limit)
const chunkResults = await Promise.all(chunk.map(fetchUser))
results.push(...chunkResults)
}
return results
}手写 Promise
js
class MyPromise {
#state = 'pending'
#value = undefined
#callbacks = []
constructor(executor) {
const resolve = (value) => {
if (this.#state !== 'pending') return
this.#state = 'fulfilled'
this.#value = value
this.#callbacks.forEach(cb => cb.onFulfilled?.(value))
}
const reject = (reason) => {
if (this.#state !== 'pending') return
this.#state = 'rejected'
this.#value = reason
this.#callbacks.forEach(cb => cb.onRejected?.(reason))
}
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handle = (fn, value, fallback) => {
try {
if (typeof fn === 'function') {
const result = fn(value)
result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
} else {
fallback(value)
}
} catch (err) {
reject(err)
}
}
if (this.#state === 'fulfilled') {
queueMicrotask(() => handle(onFulfilled, this.#value, resolve))
} else if (this.#state === 'rejected') {
queueMicrotask(() => handle(onRejected, this.#value, reject))
} else {
this.#callbacks.push({
onFulfilled: v => handle(onFulfilled, v, resolve),
onRejected: v => handle(onRejected, v, reject)
})
}
})
}
catch(onRejected) { return this.then(null, onRejected) }
finally(fn) {
return this.then(
v => MyPromise.resolve(fn()).then(() => v),
e => MyPromise.resolve(fn()).then(() => { throw e })
)
}
static resolve(value) {
return value instanceof MyPromise ? value : new MyPromise(r => r(value))
}
static reject(reason) { return new MyPromise((_, r) => r(reason)) }
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((p, i) => {
MyPromise.resolve(p).then(v => {
results[i] = v
if (++count === promises.length) resolve(results)
}, reject)
})
})
}
}错误处理最佳实践
js
// ✅ 统一错误处理封装
async function safeAsync(promise) {
try {
const data = await promise
return [null, data]
} catch (err) {
return [err, null]
}
}
// 使用:Go 风格错误处理
const [err, user] = await safeAsync(fetchUser(1))
if (err) {
console.error('获取用户失败:', err)
return
}
console.log(user)
// ✅ 全局未捕获 Promise 错误
window.addEventListener('unhandledrejection', event => {
console.error('未处理的 Promise 错误:', event.reason)
event.preventDefault() // 阻止默认的控制台错误
})总结
- Promise 解决回调地狱,提供链式调用和统一错误处理
Promise.all并行,Promise.allSettled全部等待,Promise.race竞速async/await是 Promise 的语法糖,让异步代码像同步一样可读- 循环中用
for...of串行,Promise.all + map并行 - 注意
await的串行陷阱,独立请求应并行发起