JS 异步编程实战 | 从回调地狱到 Promise/Async/Await(附代码 + 面试题)
2026年2月27日 10:41
一、为什么需要异步编程?
JavaScript 是单线程语言,同一时间只能做一件事。如果有耗时操作(如网络请求、文件读取、定时任务),就会阻塞后续代码执行。
// 同步阻塞示例
console.log('开始')
for(let i = 0; i < 1000000000; i++) {}
// 耗时操作 console.log('结束')
// 必须等待循环结束才执行
为了解决这个问题,JavaScript 提供了异步编程解决方案。
二、回调函数(Callback)—— 最基础的异步方案
2.1 基本概念
回调函数是将函数作为参数传递给另一个函数,在异步操作完成后调用。
// 模拟异步请求
function fetchData(callback) {
setTimeout(() => {
callback('数据加载完成')
}, 1000)
}
console.log('开始请求')
fetchData((data) => {
console.log(data) // 1秒后输出:数据加载完成
})
console.log('继续执行其他操作')
// 输出顺序:开始请求 → 继续执行其他操作 → 数据加载完成
2.2 回调地狱的产生
当有多个依赖的异步操作时,回调嵌套会形成"回调地狱":
// 回调地狱示例
getUserInfo(function(user) {
getOrderList(user.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
getProductInfo(detail.productId, function(product) {
console.log('最终数据:', product)
}, function(error) {
console.error('获取商品失败', error)
})
}, function(error) {
console.error('获取订单详情失败', error)
})
}, function(error) {
console.error('获取订单列表失败', error)
})
}, function(error) {
console.error('获取用户失败', error)
})
回调地狱的问题:
- 代码难以阅读和维护
- 错误处理分散
- 难以复用和调试
三、Promise —— 优雅的异步解决方案
3.1 Promise 基本用法
Promise 是 ES6 引入的异步编程解决方案,它代表一个异步操作的最终完成或失败。
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 执行异步操作
setTimeout(() => {
const success = true
if (success) {
resolve('操作成功') // 成功时调用
} else {
reject('操作失败') // 失败时调用
}
}, 1000)
})
// 使用 Promise
promise
.then(result => {
console.log(result) // 成功:操作成功
})
.catch(error => {
console.error(error) // 失败:操作失败
})
.finally(() => {
console.log('无论成功失败都会执行')
})
3.2 解决回调地狱
使用 Promise 重构上面的例子:
// 将每个异步操作封装成 Promise
function getUserInfo() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: 1, name: '张三' })
}, 1000)
})
}
function getOrderList(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([{ id: 101, name: '订单1' }, { id: 102, name: '订单2' }])
}, 1000)
})
}
function getOrderDetail(orderId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: orderId, productId: 1001, price: 299 })
}, 1000)
})
}
function getProductInfo(productId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id: productId, name: '商品名称', price: 299 })
}, 1000)
})
}
// 链式调用,告别回调地狱
getUserInfo()
.then(user => {
console.log('用户:', user)
return getOrderList(user.id)
})
.then(orders => {
console.log('订单列表:', orders)
return getOrderDetail(orders[0].id)
})
.then(detail => {
console.log('订单详情:', detail)
return getProductInfo(detail.productId)
})
.then(product => {
console.log('商品信息:', product)
})
.catch(error => {
console.error('发生错误:', error)
})
3.3 Promise 静态方法
// Promise.all - 等待所有 Promise 完成
const p1 = Promise.resolve(3)
const p2 = 42
const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo'))
Promise.all([p1, p2, p3]).then(values => {
console.log(values) // [3, 42, "foo"]
})
// Promise.race - 返回最先完成的 Promise
const promise1 = new Promise(resolve => setTimeout(resolve, 500, 'one'))
const promise2 = new Promise(resolve => setTimeout(resolve, 100, 'two'))
Promise.race([promise1, promise2]).then(value => {
console.log(value) // "two" (因为 promise2 更快)
})
// Promise.allSettled - 等待所有 Promise 完成(无论成功失败)
const promises = [
Promise.resolve('成功1'),
Promise.reject('失败2'),
Promise.resolve('成功3')
]
Promise.allSettled(promises).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value)
} else {
console.log('失败:', result.reason)
}
})
})
// Promise.any - 返回第一个成功的 Promise
const pErr = new Promise((resolve, reject) => reject('总是失败'))
const pSlow = new Promise(resolve => setTimeout(resolve, 500, '最终完成'))
const pFast = new Promise(resolve => setTimeout(resolve, 100, '很快完成'))
Promise.any([pErr, pSlow, pFast]).then(value => {
console.log(value) // "很快完成"
})
四、Async/Await —— 同步方式的异步编程
4.1 基本语法
Async/Await 是 ES2017 引入的语法糖,让异步代码看起来像同步代码。
// async 函数返回一个 Promise
async function getData() {
return '数据'
}
getData().then(result => console.log(result)) // 数据
// 使用 await 等待 Promise 完成
async function fetchUserData() {
try {
const user = await getUserInfo()
console.log('用户:', user)
const orders = await getOrderList(user.id)
console.log('订单:', orders)
const detail = await getOrderDetail(orders[0].id)
console.log('详情:', detail)
const product = await getProductInfo(detail.productId)
console.log('商品:', product)
return product
} catch (error) {
console.error('出错了:', error)
}
}
// 调用 async 函数
fetchUserData().then(result => {
console.log('最终结果:', result)
})
4.2 实战示例:模拟数据请求
// 模拟 API 请求函数
const mockAPI = (url, delay = 1000) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.1) { // 90% 成功率
resolve({
status: 200,
data: { url, timestamp: Date.now() }
})
} else {
reject(new Error(`请求 ${url} 失败`))
}
}, delay)
})
}
// 使用 async/await 实现并发请求
async function fetchMultipleData() {
try {
// 并发请求
const [userData, productData, orderData] = await Promise.all([
mockAPI('/api/user', 800),
mockAPI('/api/product', 1200),
mockAPI('/api/order', 600)
])
console.log('所有数据加载完成:')
console.log('用户数据:', userData.data)
console.log('商品数据:', productData.data)
console.log('订单数据:', orderData.data)
return { userData, productData, orderData }
} catch (error) {
console.error('数据加载失败:', error.message)
}
}
// 串行请求(依赖关系)
async function fetchDependentData() {
console.time('串行请求耗时')
const user = await mockAPI('/api/user', 1000)
console.log('第一步完成:', user.data)
const orders = await mockAPI(`/api/user/${user.data.url}/orders`, 1000)
console.log('第二步完成:', orders.data)
const details = await mockAPI(`/api/orders/${orders.data.url}/details`, 1000)
console.log('第三步完成:', details.data)
console.timeEnd('串行请求耗时')
// 总耗时约 3000ms
}
// 优化:并行处理不依赖的数据
async function fetchOptimizedData() {
console.time('优化后耗时')
// 同时发起两个独立请求
const [user, products] = await Promise.all([
mockAPI('/api/user', 1000),
mockAPI('/api/products', 1000)
])
console.log('用户和商品数据已获取')
// 依赖用户数据的请求
const orders = await mockAPI(`/api/user/${user.data.url}/orders`, 1000)
// 可以并行处理的请求
const [detail1, detail2] = await Promise.all([
mockAPI(`/api/orders/${orders.data.url}/detail1`, 500),
mockAPI(`/api/orders/${orders.data.url}/detail2`, 500)
])
console.timeEnd('优化后耗时')
// 总耗时约 2500ms
}
4.3 错误处理最佳实践
// 统一的错误处理函数
const handleAsyncError = (asyncFn) => {
return async (...args) => {
try {
return [await asyncFn(...args), null]
} catch (error) {
return [null, error]
}
}
}
// 使用错误处理包装器
const safeFetchUser = handleAsyncError(fetchUserData)
async function main() {
const [user, error] = await safeFetchUser()
if (error) {
console.error('操作失败:', error.message)
return
}
console.log('操作成功:', user)
}
// 带超时的 Promise
function withTimeout(promise, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), timeout)
})
return Promise.race([promise, timeoutPromise])
}
async function fetchWithTimeout() {
try {
const result = await withTimeout(mockAPI('/api/data', 3000), 2000)
console.log('数据:', result)
} catch (error) {
console.error('超时或失败:', error.message)
}
}
五、手写实现(面试高频)
5.1 手写 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())
}
}
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 : error => { throw error }
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected'))
}
if (x && (typeof x === 'object' || typeof x === 'function')) {
let called = false
try {
const then = x.then
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return
called = true
this.resolvePromise(promise2, y, resolve, reject)
},
error => {
if (called) return
called = true
reject(error)
}
)
} else {
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(x)
}
}
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
if (value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason))
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const result = []
let count = 0
for (let i = 0; i < promises.length; i++) {
MyPromise.resolve(promises[i]).then(
value => {
result[i] = value
count++
if (count === promises.length) resolve(result)
},
reject
)
}
})
}
static race(promises) {
return new MyPromise((resolve, reject) => {
for (const promise of promises) {
MyPromise.resolve(promise).then(resolve, reject)
}
})
}
}
5.2 手写 async/await 的简单实现
// 使用 Generator 模拟 async/await
function asyncToGenerator(generatorFn) {
return function() {
const gen = generatorFn.apply(this, arguments)
return new Promise((resolve, reject) => {
function step(key, arg) {
let result
try {
result = gen[key](arg)
} catch (error) {
reject(error)
return
}
const { value, done } = result
if (done) {
resolve(value)
} else {
Promise.resolve(value).then(
val => step('next', val),
err => step('throw', err)
)
}
}
step('next')
})
}
}
// 使用示例
const fetchData = function() {
return new Promise(resolve => {
setTimeout(() => resolve('数据'), 1000)
})
}
const getData = asyncToGenerator(function* () {
const data1 = yield fetchData()
console.log('data1:', data1)
const data2 = yield fetchData()
console.log('data2:', data2)
return '完成'
})
getData().then(result => console.log(result))
六、面试高频题
6.1 输出顺序题
// 题目1
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')
// 输出:1, 4, 3, 2
// 解释:同步代码先执行,微任务(Promise)先于宏任务(setTimeout)
// 题目2
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
async1()
new Promise((resolve) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
console.log('script end')
// 输出顺序:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
6.2 错误处理题
// 题目:如何捕获 async/await 的错误?
async function getData() {
try {
const data = await Promise.reject('出错了')
console.log(data)
} catch (error) {
console.log('捕获到:', error)
}
}
// 或使用 .catch
async function getData2() {
const data = await Promise.reject('出错了').catch(err => {
console.log('处理错误:', err)
return '默认值'
})
console.log(data) // 默认值
}
// 题目:Promise.all 的错误处理
const promises = [
Promise.resolve(1),
Promise.reject('错误'),
Promise.resolve(3)
]
Promise.all(promises)
.then(console.log)
.catch(console.error) // 输出:错误
// 如何让 Promise.all 即使有错误也返回所有结果?
Promise.allSettled(promises).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value)
} else {
console.log('失败:', result.reason)
}
})
})
6.3 并发控制题
// 题目:实现一个并发控制器,限制同时执行的 Promise 数量
class PromiseQueue {
constructor(concurrency = 2) {
this.concurrency = concurrency
this.running = 0
this.queue = []
}
add(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject })
this.run()
})
}
run() {
while (this.running < this.concurrency && this.queue.length) {
const { task, resolve, reject } = this.queue.shift()
this.running++
Promise.resolve(task())
.then(resolve, reject)
.finally(() => {
this.running--
this.run()
})
}
}
}
// 使用示例
const queue = new PromiseQueue(2)
for (let i = 0; i < 5; i++) {
queue.add(() =>
new Promise(resolve => {
setTimeout(() => {
console.log(`任务${i}完成`)
resolve(i)
}, 1000)
})
)
}
// 每2个任务并行执行
6.4 重试机制题
// 题目:实现一个函数,请求失败时自动重试
async function retryRequest(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
console.log(`第${i + 1}次尝试`)
const result = await fn()
console.log('请求成功')
return result
} catch (error) {
console.log(`第${i + 1}次失败`)
if (i === maxRetries - 1) {
throw error
}
// 等待延迟时间后重试
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
// 使用示例
let attempt = 0
const request = () => {
return new Promise((resolve, reject) => {
attempt++
if (attempt < 3) {
reject('模拟失败')
} else {
resolve('成功')
}
})
}
retryRequest(request, 3, 1000)
.then(console.log)
.catch(console.error)
七、总结与建议
7.1 异步编程演进
- 回调函数:基础但容易形成"回调地狱"
- Promise:链式调用,错误统一处理
- Async/Await:语法糖,代码更直观
7.2 使用建议
- 优先使用 async/await,代码更清晰
- 并发请求使用 Promise.all,提高性能
- 注意错误处理,不要吞掉错误
- 避免回调地狱,及时重构代码
- 理解事件循环,掌握执行顺序
7.3 面试准备
- 掌握三种异步方案的原理和用法
- 能够手写简单的 Promise
- 理解宏任务和微任务的执行顺序
- 熟悉常见的异步编程场景和解决方案
- 能够处理并发控制和错误重试
异步编程是 JavaScript 的核心特性,掌握好这块内容不仅对面试有帮助,更能提升实际开发中的代码质量。