引言

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异步编程的最佳实践:

核心原则:

  1. 优先使用async/await,代码更易读
  2. 合理使用Promise.all处理并发
  3. 实现适当的错误处理机制
  4. 避免不必要的串行等待

性能优化:

  1. 并行处理独立任务
  2. 使用缓存减少重复请求
  3. 实现请求合并和批量处理
  4. 考虑使用Web Workers处理重任务

代码质量:

  1. 保持异步操作的可追踪性
  2. 实现重试和超时机制
  3. 避免回调地狱
  4. 提供清晰的错误信息

掌握这些异步编程技巧,将帮助你构建更健壮、更高效的JavaScript应用。


相关资源: