JavaScript异步编程最佳实践:从回调到async/await
本文全面介绍了JavaScript异步编程的发展,从回调函数到Promise再到async/await,包含大量实战技巧和性能优化建议。
JavaScript作为单线程语言,异步编程是其核心特性之一。从最初的回调函数到Promise,再到现代的async/await语法,JavaScript的异步编程模式不断演进。本文将带你深入了解异步编程的各个阶段,掌握最佳实践,写出更优雅、更高效的异步代码。
异步编程的演进历程#
1. 回调函数时代#
1
2
3
4
5
6
7
8
9
10
11
12
| // 传统的回调方式
function fetchData(callback) {
setTimeout(() => {
const data = { id: 1, name: 'John' }
callback(data)
}, 1000)
}
fetchData((data) => {
console.log('获取到数据:', data)
// 继续下一步操作...
})
|
回调地狱问题:
1
2
3
4
5
6
7
8
9
10
| // 难以维护的回调嵌套
loadUser(userId, (user) => {
loadPosts(user.id, (posts) => {
loadComments(posts[0].id, (comments) => {
loadAuthor(comments[0].authorId, (author) => {
console.log('最终结果:', author)
})
})
})
})
|
2. Promise的革命#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // 使用Promise改写
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'John' }
resolve(data)
}, 1000)
})
}
// 链式调用
fetchData()
.then(data => {
console.log('获取到数据:', data)
return loadPosts(data.id)
})
.then(posts => {
console.log('获取到文章:', posts)
return loadComments(posts[0].id)
})
.then(comments => {
console.log('获取到评论:', comments)
})
.catch(error => {
console.error('发生错误:', error)
})
|
3. async/await的优雅#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 使用async/await,代码更清晰
async function loadAllData() {
try {
const user = await fetchData()
console.log('获取到数据:', user)
const posts = await loadPosts(user.id)
console.log('获取到文章:', posts)
const comments = await loadComments(posts[0].id)
console.log('获取到评论:', comments)
} catch (error) {
console.error('发生错误:', error)
}
}
|
Promise深入理解#
Promise的三种状态#
1
2
3
4
5
6
7
| const promise = new Promise((resolve, reject) => {
// pending -> fulfilled
// pending -> rejected
// 一旦状态改变就不可逆
})
console.log(promise) // Promise { <pending> }
|
Promise的静态方法#
Promise.all()#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // 并行执行多个Promise
async function fetchAllData() {
try {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
])
console.log('所有数据加载完成')
return { users, posts, comments }
} catch (error) {
console.error('某个请求失败:', error)
}
}
// 处理部分失败的情况
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
fetchComments()
])
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value)
} else {
console.log('失败:', result.reason)
}
})
|
Promise.race()#
1
2
3
4
5
6
7
8
9
| // 返回最快完成的Promise结果
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), 5000)
)
const result = await Promise.race([
fetchData(),
timeout
])
|
Promise.any()#
1
2
3
4
5
6
7
8
9
10
11
| // 返回第一个成功的Promise
try {
const result = await Promise.any([
fetchFromPrimary(),
fetchFromSecondary(),
fetchFromCache()
])
console.log('成功获取数据:', result)
} catch (error) {
console.error('所有数据源都失败了:', error)
}
|
async/await最佳实践#
1. 错误处理策略#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // 统一错误处理中间件
async function withErrorHandling(fn) {
try {
return await fn()
} catch (error) {
console.error('操作失败:', error)
// 可以添加错误上报逻辑
throw error // 重新抛出或返回默认值
}
}
// 使用高阶函数包装
const safeFetchData = () => withErrorHandling(async () => {
const response = await fetch('/api/data')
return response.json()
})
|
2. 优雅的重试机制#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| async function retry(fn, maxAttempts = 3, delay = 1000) {
let lastError
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn()
} catch (error) {
lastError = error
if (attempt === maxAttempts) {
throw lastError
}
// 指数退避
const waitTime = delay * Math.pow(2, attempt - 1)
await new Promise(resolve => setTimeout(resolve, waitTime))
}
}
}
// 使用示例
const data = await retry(async () => {
const response = await fetch('/api/unstable')
if (!response.ok) throw new Error('请求失败')
return response.json()
}, 5, 500)
|
3. 并发控制#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // 限制并发数量的通用函数
async function limitConcurrency(tasks, limit = 3) {
const results = []
const executing = []
for (const task of tasks) {
const promise = task().then(result => {
executing.splice(executing.indexOf(promise), 1)
return result
})
results.push(promise)
executing.push(promise)
if (executing.length >= limit) {
await Promise.race(executing)
}
}
return Promise.all(results)
}
// 使用示例
const urls = Array(100).fill('/api/data')
const fetchTasks = urls.map(url => () => fetch(url))
const responses = await limitConcurrency(fetchTasks, 10)
|
4. 超时处理#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| async function withTimeout(promise, timeoutMs) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('操作超时')), timeoutMs)
})
return Promise.race([promise, timeoutPromise])
}
// 使用示例
try {
const data = await withTimeout(fetch('/api/slow'), 5000)
console.log('获取数据成功:', data)
} catch (error) {
if (error.message === '操作超时') {
console.log('请求超时,使用缓存数据')
// 回退逻辑
}
}
|
实战场景应用#
1. 数据缓存策略#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| class DataCache {
constructor() {
this.cache = new Map()
this.pending = new Map()
}
async get(key, fetcher, ttl = 60000) {
// 检查缓存
if (this.cache.has(key)) {
const { data, timestamp } = this.cache.get(key)
if (Date.now() - timestamp < ttl) {
return data
}
}
// 检查是否有正在进行的请求
if (this.pending.has(key)) {
return this.pending.get(key)
}
// 发起新请求
const promise = fetcher()
.then(data => {
this.cache.set(key, {
data,
timestamp: Date.now()
})
this.pending.delete(key)
return data
})
.catch(error => {
this.pending.delete(key)
throw error
})
this.pending.set(key, promise)
return promise
}
}
// 使用示例
const cache = new DataCache()
async function getUserProfile(userId) {
return cache.get(
`user:${userId}`,
async () => {
const response = await fetch(`/api/users/${userId}`)
return response.json()
}
)
}
|
2. 批量请求优化#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| // 请求合并器
class RequestBatcher {
constructor(batchSize = 10, delay = 100) {
this.batchSize = batchSize
this.delay = delay
this.queue = []
this.timer = null
}
add(request) {
return new Promise((resolve, reject) => {
this.queue.push({ request, resolve, reject })
if (this.queue.length >= this.batchSize) {
this.flush()
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.delay)
}
})
}
async flush() {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
if (this.queue.length === 0) return
const batch = this.queue.splice(0, this.batchSize)
const requests = batch.map(item => item.request)
try {
const results = await Promise.all(requests)
batch.forEach((item, index) => {
item.resolve(results[index])
})
} catch (error) {
batch.forEach(item => {
item.reject(error)
})
}
}
}
// 使用示例
const batcher = new RequestBatcher()
async function fetchUser(userId) {
return batcher.add(() =>
fetch(`/api/users/${userId}`).then(res => res.json())
)
}
// 多个调用会被合并
const users = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3),
// ...
])
|
3. 流式数据处理#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| // 处理大量数据的流式方案
async function processLargeData(dataStream, processor) {
const results = []
for await (const chunk of dataStream) {
const processedChunk = await processor(chunk)
results.push(processedChunk)
// 让出控制权,避免阻塞
await new Promise(resolve => setTimeout(resolve, 0))
}
return results
}
// 模拟数据流
async function* dataGenerator(items) {
for (const item of items) {
yield item
await new Promise(resolve => setTimeout(resolve, 100))
}
}
// 使用示例
const items = Array(1000).fill(0).map((_, i) => i)
const stream = dataGenerator(items)
const processedData = await processLargeData(
stream,
async (item) => {
// 处理每个数据项
return item * 2
}
)
|
性能优化技巧#
1. 避免不必要的await#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // ❌ 不好的实践
async function processItems(items) {
const results = []
for (const item of items) {
const result = await processItem(item)
results.push(result)
}
return results
}
// ✅ 好的实践 - 并行处理
async function processItems(items) {
return Promise.all(items.map(item => processItem(item)))
}
// ✅ 更好的实践 - 限制并发
async function processItems(items) {
return limitConcurrency(items.map(item => () => processItem(item)), 10)
}
|
2. 缓存Promise对象#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| class PromiseCache {
constructor() {
this.cache = new Map()
}
async get(key, factory) {
// 如果已有Promise,复用它
if (this.cache.has(key)) {
return this.cache.get(key)
}
// 创建新的Promise并缓存
const promise = factory()
this.cache.set(key, promise)
// 无论成功失败,最终清理缓存
promise.finally(() => {
this.cache.delete(key)
})
return promise
}
}
// 使用示例
const promiseCache = new PromiseCache()
async function getConfig() {
return promiseCache.get('config', async () => {
const response = await fetch('/api/config')
return response.json()
})
}
|
3. 使用Web Workers处理重任务#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // 主线程
async function heavyComputation(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('computation-worker.js')
worker.postMessage(data)
worker.onmessage = (event) => {
resolve(event.data)
worker.terminate()
}
worker.onerror = (error) => {
reject(error)
worker.terminate()
}
// 超时处理
setTimeout(() => {
worker.terminate()
reject(new Error('计算超时'))
}, 30000)
})
}
// computation-worker.js
self.onmessage = function(event) {
const data = event.data
// 执行重计算任务
const result = performHeavyCalculation(data)
self.postMessage(result)
}
|
常见陷阱与解决方案#
1. 循环中的异步操作#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // ❌ 错误:没有等待所有异步操作完成
async function processItems(items) {
items.forEach(async item => {
await processItem(item)
})
console.log('处理完成') // 实际上还没完成
}
// ✅ 正确:使用for...of
async function processItems(items) {
for (const item of items) {
await processItem(item)
}
console.log('处理完成')
}
// ✅ 并行处理
async function processItems(items) {
await Promise.all(items.map(item => processItem(item)))
console.log('处理完成')
}
|
2. 异步函数中的条件语句#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // ❌ 可能导致意外行为
async function getUserData(userId) {
let user
if (userId) {
user = await fetchUser(userId)
}
// user 可能是 undefined
return user
}
// ✅ 明确处理所有情况
async function getUserData(userId) {
if (!userId) {
return null
}
const user = await fetchUser(userId)
return user || null
}
|
3. 错误处理的最佳实践#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| // ❌ 忽略错误
async function unsafeOperation() {
const result = await riskyOperation()
// 没有错误处理
}
// ✅ 适当的错误处理
async function safeOperation() {
try {
const result = await riskyOperation()
return result
} catch (error) {
console.error('操作失败:', error)
// 可以返回默认值或重新抛出
return null
}
}
// ✅ 使用专门的错误处理函数
async function withFallback(operation, fallback) {
try {
return await operation()
} catch (error) {
console.warn('操作失败,使用备用方案:', error.message)
return fallback ? fallback() : null
}
}
|
JavaScript异步编程的最佳实践:
核心原则:
- 优先使用async/await,代码更易读
- 合理使用Promise.all处理并发
- 实现适当的错误处理机制
- 避免不必要的串行等待
性能优化:
- 并行处理独立任务
- 使用缓存减少重复请求
- 实现请求合并和批量处理
- 考虑使用Web Workers处理重任务
代码质量:
- 保持异步操作的可追踪性
- 实现重试和超时机制
- 避免回调地狱
- 提供清晰的错误信息
掌握这些异步编程技巧,将帮助你构建更健壮、更高效的JavaScript应用。
相关资源: