JavaScript异步编程最佳实践:从回调到async/await的完整指南

JavaScript的异步编程是现代Web开发的核心技能。从早期的回调函数到Promise,再到async/await,JavaScript的异步编程模式经历了显著的演进。本文将深入探讨这些模式,并提供实际开发中的最佳实践。

异步编程的演进历程

1. 回调函数时代

基础回调模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 简单的异步回调
function fetchData(callback) {
  setTimeout(() => {
    callback('Hello, World!')
  }, 1000)
}

fetchData((result) => {
  console.log(result)
})

回调地狱问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 回调地狱示例
fetchUser((user) => {
  fetchPosts(user.id, (posts) => {
    fetchComments(posts[0].id, (comments) => {
      processComments(comments, (result) => {
        console.log('处理完成:', result)
      })
    })
  })
})

回调解决方案

 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
// 使用命名函数避免回调地狱
function fetchUser(callback) {
  setTimeout(() => {
    callback({ id: 1, name: 'John' })
  }, 1000)
}

function fetchPosts(userId, callback) {
  setTimeout(() => {
    callback([{ id: 1, title: 'Post 1' }])
  }, 1000)
}

function fetchComments(postId, callback) {
  setTimeout(() => {
    callback(['Comment 1', 'Comment 2'])
  }, 1000)
}

function processComments(comments, callback) {
  setTimeout(() => {
    callback(comments.length)
  }, 1000)
}

// 线性回调链
fetchUser((user) => {
  fetchPosts(user.id, (posts) => {
    fetchComments(posts[0].id, (comments) => {
      processComments(comments, (result) => {
        console.log('处理完成:', result)
      })
    })
  })
})

2. Promise时代

基础Promise

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 创建Promise
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Hello, World!')
    }, 1000)
  })
}

fetchData()
  .then(result => console.log(result))
  .catch(error => console.error(error))

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
function fetchUser() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ id: 1, name: 'John' })
    }, 1000)
  })
}

function fetchPosts(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([{ id: 1, title: 'Post 1' }])
    }, 1000)
  })
}

function fetchComments(postId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(['Comment 1', 'Promise Comment 2'])
    }, 1000)
  })
}

// Promise链避免回调地狱
fetchUser()
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => processComments(comments))
  .then(result => console.log('处理完成:', result))
  .catch(error => console.error('错误:', error))

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Promise.all - 并行执行,全部成功才成功
const promises = [
  fetchData('/api/user'),
  fetchData('/api/posts'),
  fetchData('/api/comments')
]

Promise.all(promises)
  .then(results => {
    console.log('所有请求成功:', results)
  })
  .catch(error => {
    console.error('有请求失败:', error)
  })

// Promise.race - 竞争执行,第一个完成就返回
const racePromises = [
  fetchData('/api/fast'),
  fetchData('/api/slow'),
  fetchData('/api/medium')
]

Promise.race(racePromises)
  .then(result => {
    console.log('最快的响应:', result)
  })
  .catch(error => {
    console.error('所有请求都失败:', error)
  })

// Promise.allSettled - 全部完成,不管成功失败
const settledPromises = [
  fetchData('/api/success1'),
  fetchData('/api/fail1'),
  fetchData('/api/success2')
]

Promise.allSettled(settledPromises)
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`请求${index}成功:`, result.value)
      } else {
        console.log(`请求${index}失败:`, result.reason)
      }
    })
  })

3. async/await时代

基本async/await语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
async function fetchData() {
  try {
    const response = await fetch('/api/data')
    const data = await response.json()
    return data
  } catch (error) {
    console.error('获取数据失败:', error)
    throw error
  }
}

// 使用
fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error))

顺序执行异步操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
async function processUserData() {
  const user = await fetchUser()
  const posts = await fetchPosts(user.id)
  const comments = await fetchComments(posts[0].id)
  return { user, posts, comments }
}

// 并行执行异步操作
async function fetchAllData() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(1),
    fetchComments(1)
  ])
  return { user, posts, comments }
}

Promise深入理解

1. Promise状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const promise = new Promise((resolve, reject) => {
  // pending -> fulfilled/rejected
})

// 状态检查
console.log(promise) // Promise { <pending> }

// 状态处理
promise
  .then(value => {
    console.log('fulfilled:', value) // fulfilled状态
  })
  .catch(error => {
    console.log('rejected:', error) // rejected状态
  })
  .finally(() => {
    console.log('settled:') // 无论成功失败都会执行
  })

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 错误传播
function fetchWithRetry(url, maxRetries = 3) {
  return new Promise((resolve, reject) => {
    let retries = 0

    const attempt = () => {
      fetch(url)
        .then(response => {
          if (!response.ok) {
            throw new Error(`HTTP ${response.status}`)
          }
          return response.json()
        })
        .then(data => resolve(data))
        .catch(error => {
          retries++
          if (retries <= maxRetries) {
            console.log(`重试第${retries}次`)
            setTimeout(attempt, 1000 * retries)
          } else {
            reject(error)
          }
        })
    }

    attempt()
  })
}

// 错误处理最佳实践
function robustFetch(url, options = {}) {
  return fetch(url, options)
    .then(response => {
      if (!response.ok) {
        // 创建包含更多错误信息的Error对象
        const error = new Error(`HTTP ${response.status}: ${response.statusText}`)
        error.status = response.status
        error.statusText = response.statusText
        error.response = response
        throw error
      }
      return response.json()
    })
    .catch(error => {
      // 区分网络错误和其他错误
      if (error.name === 'TypeError' || error.message.includes('Failed to fetch')) {
        throw new Error('网络错误,请检查网络连接')
      }
      throw error
    })
}

async/await最佳实践

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
// 统一错误处理
class ApiError extends Error {
  constructor(message, statusCode, data) {
    super(message)
    this.name = 'ApiError'
    this.statusCode = statusCode
    this.data = data
  }
}

async function apiCall(url, options = {}) {
  try {
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${getAuthToken()}`
      },
      ...options
    })

    if (!response.ok) {
      throw new ApiError(
        `API请求失败: ${response.status} ${response.statusText}`,
        response.status
      )
    }

    return await response.json()
  } catch (error) {
    if (error instanceof ApiError) {
      throw error // 重新抛出API错误
    }

    // 处理网络错误
    if (error.name === 'TypeError') {
      throw new ApiError('网络连接失败', 0)
    }

    throw error // 重新抛出其他错误
  }
}

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
// 限制并发数量
class ConcurrencyLimiter {
  constructor(limit = 3) {
    this.limit = limit
    this.running = 0
    this.queue = []
  }

  async add(async fn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject })
      this.processQueue()
    })
  }

  async processQueue() {
    if (this.running >= this.limit || this.queue.length === 0) {
      return
    }

    this.running++
    const { fn, resolve, reject } = this.queue.shift()

    try {
      const result = await fn()
      resolve(result)
    } catch (error) {
      reject(error)
    } finally {
      this.running--
      this.processQueue()
    }
  }
}

// 使用示例
const limiter = new ConcurrencyLimiter(5)

async function fetchMultipleUrls(urls) {
  const promises = urls.map(url =>
    limiter.add(() => fetch(url).then(res => res.json())
  )
  return Promise.all(promises)
}

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
// 超时包装器
function timeout(promise, delay) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('操作超时')), delay)
    )
  ])
}

// 使用示例
async function fetchWithTimeout(url, delay = 5000) {
  try {
    const response = await timeout(
      fetch(url),
      delay
    )
    return response.json()
  } catch (error) {
    if (error.message === '操作超时') {
      console.log(`请求${url}超时`)
    }
    throw error
  }
}

实际应用场景

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
class DataCache {
  constructor(ttl = 300000) { // 5分钟
    this.cache = new Map()
    this.ttl = ttl
  }

  async get(key, fetchFn) {
    const cached = this.cache.get(key)

    if (cached && Date.now() - cached.timestamp < this.ttl) {
      console.log('使用缓存数据:', key)
      return cached.data
    }

    console.log('获取新数据:', key)
    try {
      const data = await fetchFn()
      this.cache.set(key, {
        data,
        timestamp: Date.now()
      })
      return data
    } catch (error) {
      console.error('获取数据失败:', error)
      throw error
    }
  }
}

// 使用示例
const cache = new DataCache()

async function getUserData(userId) {
  return cache.get(`user-${userId}`, () =>
    fetch(`/api/users/${userId}`).then(res => res.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
async function uploadFile(file, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    const formData = new FormData()

    formData.append('file', file)

    xhr.upload.onprogress = (event) => {
      if (event.lengthComputable) {
        const progress = Math.round((event.loaded / event.total) * 100)
        onProgress(progress)
      }
    }

    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      } else {
        reject(new Error(`上传失败: ${xhr.status}`))
      }
    }

    xhr.onerror = () => reject(new Error('网络错误'))

    xhr.open('POST', '/api/upload')
    xhr.send(formData)
  })
}

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 智能轮询
class SmartPoller {
  constructor(fetchFn, options = {}) {
    this.fetchFn = fetchFn
    this.interval = options.interval || 5000
    this.maxInterval = options.maxInterval || 30000
    this.backoffFactor = options.backoffFactor || 1.5
    this.maxRetries = options.maxRetries || 10
    this.currentInterval = this.interval
    this.retryCount = 0
    this.timeoutId = null
  }

  start(callback) {
    const poll = async () => {
      try {
        const result = await this.fetchFn()
        this.retryCount = 0
        this.currentInterval = this.interval
        callback(null, result)
      } catch (error) {
        this.retryCount++

        if (this.retryCount > this.maxRetries) {
          callback(error, null)
          return
        }

        this.currentInterval = Math.min(
          this.currentInterval * this.backoffFactor,
          this.maxInterval
        )

        console.log(`第${this.retryCount}次重试,${this.currentInterval}ms后重试`)

        this.timeoutId = setTimeout(poll, this.currentInterval)
      }
    }

    this.timeoutId = setTimeout(poll, 0)
  }

  stop() {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId)
      this.timeoutId = null
    }
  }
}

错误处理和调试

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
// 自定义错误类型
class NetworkError extends Error {
  constructor(message, code) {
    super(message)
    this.name = 'NetworkError'
    this.code = code
  }
}

class ValidationError extends Error {
  constructor(message, field) {
    super(message)
    this.name = 'ValidationError'
    this.field = field
  }
}

// 错误处理中间件
function errorHandler(error, req, res, next) {
  if (error instanceof NetworkError) {
    res.status(503).json({
      error: '网络服务不可用',
      code: error.code,
      message: error.message
    })
  } else if (error instanceof ValidationError) {
    res.status(400).json({
      error: '验证失败',
      field: error.field,
      message: error.message
    })
  } else {
    res.status(500).json({
      error: '服务器内部错误',
      message: error.message
    })
  }
}

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
// 错误追踪包装器
function withErrorTracking(fn) {
  return async (...args) => {
    try {
      return await fn(...args)
    } catch (error) {
      // 记录错误上下文
      console.error('异步操作失败:', {
        error: error.message,
        stack: error.stack,
        args: args,
        timestamp: new Date().toISOString()
      })

      // 发送错误到监控服务
      trackError(error)

      throw error
    }
  }
}

// 使用示例
const safeFetch = withErrorTracking(fetch)

async function fetchUserData(userId) {
  const response = await safeFetch(`/api/users/${userId}`)
  return response.json()
}

性能优化

1. 避免不必要的Promise创建

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 不好的做法:每次调用都创建新的Promise
function getData() {
  return new Promise((resolve) => {
    resolve('data')
  })
}

// 好的做法:缓存Promise或使用函数
const dataPromise = new Promise((resolve) => {
  resolve('data')
})

function getData() {
  return dataPromise
}

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
// 不好的做法:串行执行
async function getDataSerial() {
  const user = await fetchUser()
  const posts = await fetchPosts(user.id)
  const comments = await fetchComments(posts[0].id)
  return { user, posts, comments }
}

// 好的做法:并行执行
async function getDataParallel() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(1),
    fetchComments(1)
  ])
  return { user, posts, comments }
}

// 部分并行执行
async function getDataMixed() {
  const [user, posts] = await Promise.all([
    fetchUser(),
    fetchPosts(1)
  ])

  // 等posts加载完成后再获取comments
  const comments = await fetchComments(posts[0].id)
  return { user, posts, comments }
}

测试异步代码

1. 测试Promise

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Jest测试Promise
test('Promise resolves with correct data', async () => {
  const result = await fetchData()
  expect(result).toBe('Hello, World!')
})

test('Promise rejects with error', async () => {
  await expect(fetchData()).rejects.toThrow('Network error')
})

// 测试Promise.all
test('Promise.all resolves when all promises resolve', async () => {
  const promises = [
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3)
  ]
  const result = await Promise.all(promises)
  expect(result).toEqual([1, 2, 3])
})

2. 测试async/await

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Jest测试async函数
describe('User API', () => {
  test('should fetch user data', async () => {
    const userData = await fetchUser(1)
    expect(userData).toHaveProperty('id')
    expect(userData).toHaveProperty('name')
  })

  test('should handle network errors', async () => {
    await expect(fetchUser(999)).rejects.toThrow('User not found')
  })
})

// Mock测试异步操作
jest.mock('./api')
test('should call API with correct parameters', async () => {
  const result = await fetchUser(1)
  expect(api.fetchUser).toHaveBeenCalledWith(1)
  expect(result).toEqual(mockUserData)
})

总结

JavaScript异步编程的演进为我们提供了越来越优雅和强大的工具:

  1. 回调函数:基础但容易产生回调地狱
  2. Promise:解决了回调地狱,支持链式调用和错误处理
  3. async/await:让异步代码看起来像同步代码,更易读易维护

最佳实践要点:

  • 优先使用async/await编写异步代码
  • 合理使用Promise.all、Promise.race等并发控制方法
  • 实现完善的错误处理和超时机制
  • 注意性能优化,避免不必要的Promise创建
  • 编写可测试的异步代码

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