异步编程最佳实践

掌握异步编程的最佳实践,写出更健壮、更高效的异步代码。


1. 一句话概括主题

异步编程最佳实践是一系列经验和技巧,帮助你写出更清晰、更健壮、更高效的异步代码。


2. 它是什么

就像做菜有”最佳实践”(比如先热锅再放油),写异步代码也有”最佳实践”:

  • 错误处理:就像做菜要准备灭火器,写异步代码要处理错误
  • 避免阻塞:就像做菜时不要一直盯着一个锅,要合理利用时间
  • 代码清晰:就像做菜步骤要清晰,代码也要易读

这些”最佳实践”是很多开发者总结出来的经验,遵循它们可以让你:

  • 代码更不容易出错
  • 代码更容易维护
  • 代码性能更好

简单理解:异步编程最佳实践 = 写异步代码的”经验总结”和”注意事项”。


3. 能解决什么问题 + 为什么重要

解决的问题

  1. 错误处理混乱:异步操作的错误容易被忽略或处理不当
  2. 性能问题:不当的异步处理导致程序变慢
  3. 代码难以维护:异步代码结构混乱,难以理解和修改
  4. 资源泄漏:异步操作没有正确清理,导致内存泄漏

为什么重要

  • 提高代码质量:遵循最佳实践可以写出更健壮的代码
  • 减少 Bug:避免常见的异步编程陷阱
  • 提高性能:优化异步操作的执行方式
  • 团队协作:统一的实践让团队代码更一致

4. 核心知识点拆解

4.1 错误处理最佳实践

总是处理错误

// ❌ 错误:没有错误处理
async function fetchData() {
  const data = await fetch('/api/data');
  return data.json();
}
 
// ✅ 正确:总是处理错误
async function fetchData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error(`HTTP错误!状态码:${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('获取数据失败:', error);
    // 根据错误类型处理
    if (error.name === 'TypeError') {
      // 网络错误
    } else if (error.name === 'SyntaxError') {
      // JSON 解析错误
    }
    throw error; // 重新抛出,让调用者处理
  }
}

使用统一的错误处理

// 创建统一的错误处理函数
function handleAsyncError(error, context) {
  console.error(`[${context}] 错误:`, error);
  
  // 可以在这里添加错误上报
  // reportError(error, context);
  
  // 根据错误类型返回用户友好的消息
  if (error.message.includes('网络')) {
    return '网络连接失败,请检查网络';
  } else if (error.message.includes('超时')) {
    return '请求超时,请稍后重试';
  } else {
    return '操作失败,请稍后重试';
  }
}
 
// 使用
async function loadData() {
  try {
    const data = await fetchData();
    return data;
  } catch (error) {
    const message = handleAsyncError(error, 'loadData');
    alert(message);
    throw error;
  }
}

4.2 并发控制最佳实践

并行执行独立操作

// ❌ 错误:串行执行,很慢
async function loadAllData() {
  const users = await fetch('/api/users');      // 等 1 秒
  const posts = await fetch('/api/posts');      // 再等 1 秒
  const comments = await fetch('/api/comments'); // 再等 1 秒
  // 总共 3 秒
}
 
// ✅ 正确:并行执行,很快
async function loadAllData() {
  const [users, posts, comments] = await Promise.all([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  // 总共 1 秒(三个请求同时进行)
}

控制并发数量

// 限制同时进行的请求数量
async function limitConcurrency(tasks, limit) {
  const results = [];
  
  for (let i = 0; i < tasks.length; i += limit) {
    // 每次处理 limit 个任务
    const batch = tasks.slice(i, i + limit);
    const batchResults = await Promise.all(
      batch.map(task => task())
    );
    results.push(...batchResults);
  }
  
  return results;
}
 
// 使用
const urls = ['/api/1', '/api/2', '/api/3', '/api/4', '/api/5'];
const tasks = urls.map(url => () => fetch(url));
 
// 每次最多 2 个并发请求
const results = await limitConcurrency(tasks, 2);

4.3 超时处理

// 为异步操作添加超时
function withTimeout(promise, timeoutMs) {
  const timeout = new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error('操作超时'));
    }, timeoutMs);
  });
  
  return Promise.race([promise, timeout]);
}
 
// 使用
async function fetchWithTimeout(url) {
  try {
    const response = await withTimeout(
      fetch(url),
      5000 // 5 秒超时
    );
    return await response.json();
  } catch (error) {
    if (error.message === '操作超时') {
      console.error('请求超时');
    } else {
      console.error('请求失败:', error);
    }
    throw error;
  }
}

4.4 取消异步操作

// 创建可取消的 Promise
function cancellablePromise(promise) {
  let cancelled = false;
  
  const wrappedPromise = new Promise((resolve, reject) => {
    promise
      .then(result => {
        if (!cancelled) {
          resolve(result);
        }
      })
      .catch(error => {
        if (!cancelled) {
          reject(error);
        }
      });
  });
  
  return {
    promise: wrappedPromise,
    cancel: () => {
      cancelled = true;
    }
  };
}
 
// 使用
const { promise, cancel } = cancellablePromise(
  fetch('/api/data')
);
 
// 5 秒后取消
setTimeout(() => {
  cancel();
  console.log('请求已取消');
}, 5000);
 
promise
  .then(data => console.log('数据:', data))
  .catch(error => console.error('错误:', error));

4.5 重试机制

// 带重试的异步函数
async function retry(fn, maxRetries = 3, delay = 1000) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) {
        throw error; // 最后一次重试失败,抛出错误
      }
      console.log(`第 ${i + 1} 次尝试失败,${delay}ms 后重试...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
 
// 使用
async function fetchWithRetry(url) {
  return await retry(
    () => fetch(url).then(r => r.json()),
    3,    // 最多重试 3 次
    1000  // 每次重试间隔 1 秒
  );
}

4.6 避免内存泄漏

// ❌ 错误:没有清理定时器
function startPolling() {
  setInterval(async () => {
    const data = await fetch('/api/data');
    updateUI(data);
  }, 1000);
  // 如果组件卸载,定时器还在运行
}
 
// ✅ 正确:保存引用,可以清理
function startPolling() {
  const intervalId = setInterval(async () => {
    const data = await fetch('/api/data');
    updateUI(data);
  }, 1000);
  
  // 返回清理函数
  return () => {
    clearInterval(intervalId);
  };
}
 
// 使用
const cleanup = startPolling();
 
// 组件卸载时清理
// cleanup();

5. 示例代码(可运行 + 逐行注释)

示例 1:完整的错误处理

// 封装带完整错误处理的 API 请求
async function apiRequest(url, options = {}) {
  try {
    // 添加超时
    const controller = new AbortController();
    const timeoutId = setTimeout(() => {
      controller.abort();
    }, options.timeout || 5000);
    
    // 发送请求
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    
    // 清除超时定时器
    clearTimeout(timeoutId);
    
    // 检查响应状态
    if (!response.ok) {
      throw new Error(`HTTP错误!状态码:${response.status}`);
    }
    
    // 解析 JSON
    const data = await response.json();
    return data;
    
  } catch (error) {
    // 处理不同类型的错误
    if (error.name === 'AbortError') {
      throw new Error('请求超时');
    } else if (error.name === 'TypeError') {
      throw new Error('网络错误,请检查网络连接');
    } else if (error.name === 'SyntaxError') {
      throw new Error('服务器返回的数据格式错误');
    } else {
      throw error;
    }
  }
}
 
// 使用
async function loadUserData() {
  try {
    const user = await apiRequest('/api/user', { timeout: 3000 });
    console.log('用户数据:', user);
    return user;
  } catch (error) {
    console.error('加载失败:', error.message);
    // 显示用户友好的错误提示
    alert(error.message);
    throw error;
  }
}

示例 2:并发控制

// 限制并发数量的请求函数
async function fetchWithLimit(urls, limit = 3) {
  const results = [];
  const executing = [];
  
  for (const url of urls) {
    // 创建 Promise
    const promise = fetch(url)
      .then(response => response.json())
      .then(data => {
        // 请求完成后,从执行队列中移除
        executing.splice(executing.indexOf(promise), 1);
        return data;
      });
    
    results.push(promise);
    executing.push(promise);
    
    // 如果达到并发限制,等待一个完成
    if (executing.length >= limit) {
      await Promise.race(executing);
    }
  }
  
  // 等待所有请求完成
  return Promise.all(results);
}
 
// 使用
const urls = [
  '/api/user/1',
  '/api/user/2',
  '/api/user/3',
  '/api/user/4',
  '/api/user/5'
];
 
fetchWithLimit(urls, 2) // 每次最多 2 个并发
  .then(results => {
    console.log('所有请求完成:', results);
  })
  .catch(error => {
    console.error('请求失败:', error);
  });

示例 3:带重试的请求

// 带重试和退避策略的请求函数
async function fetchWithRetry(url, options = {}) {
  const {
    maxRetries = 3,
    initialDelay = 1000,
    backoffFactor = 2
  } = options;
  
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return await response.json();
      
    } catch (error) {
      lastError = error;
      
      // 最后一次尝试失败,不再重试
      if (attempt === maxRetries - 1) {
        break;
      }
      
      // 计算延迟时间(指数退避)
      const delay = initialDelay * Math.pow(backoffFactor, attempt);
      console.log(`第 ${attempt + 1} 次尝试失败,${delay}ms 后重试...`);
      
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}
 
// 使用
fetchWithRetry('/api/data', {
  maxRetries: 3,
  initialDelay: 1000,
  backoffFactor: 2
})
  .then(data => {
    console.log('成功:', data);
  })
  .catch(error => {
    console.error('最终失败:', error);
  });

6. 常见错误与踩坑

错误 1:忘记处理 Promise 拒绝

// ❌ 错误:Promise 被拒绝但没有处理
async function fetchData() {
  const data = await fetch('/api/data');
  return data.json();
}
 
fetchData(); // 如果失败,会有未处理的 Promise 拒绝警告
 
// ✅ 正确:总是处理错误
fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

错误 2:在循环中使用 await

// ❌ 错误:串行执行,很慢
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) {
  const results = await Promise.all(
    items.map(item => processItem(item))
  );
  return results;
}

错误 3:没有清理异步操作

// ❌ 错误:定时器没有清理
function startPolling() {
  setInterval(() => {
    fetchData();
  }, 1000);
  // 组件卸载后,定时器还在运行
}
 
// ✅ 正确:提供清理方法
function startPolling() {
  const intervalId = setInterval(() => {
    fetchData();
  }, 1000);
  
  return () => clearInterval(intervalId);
}

错误 4:错误处理不当

// ❌ 错误:吞掉错误
async function fetchData() {
  try {
    const data = await fetch('/api/data');
    return data.json();
  } catch (error) {
    // 错误被吞掉了,调用者不知道
    console.error(error);
  }
}
 
// ✅ 正确:重新抛出错误或返回错误状态
async function fetchData() {
  try {
    const data = await fetch('/api/data');
    return { success: true, data: await data.json() };
  } catch (error) {
    console.error(error);
    return { success: false, error: error.message };
  }
}

7. 实际应用场景

场景 1:API 请求封装

// 完整的 API 请求封装
class ApiClient {
  constructor(baseURL, options = {}) {
    this.baseURL = baseURL;
    this.timeout = options.timeout || 5000;
    this.retries = options.retries || 3;
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    
    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), this.timeout);
      
      const response = await fetch(url, {
        ...options,
        signal: controller.signal
      });
      
      clearTimeout(timeoutId);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return await response.json();
      
    } catch (error) {
      if (error.name === 'AbortError') {
        throw new Error('请求超时');
      }
      throw error;
    }
  }
  
  async requestWithRetry(endpoint, options = {}) {
    let lastError;
    
    for (let i = 0; i < this.retries; i++) {
      try {
        return await this.request(endpoint, options);
      } catch (error) {
        lastError = error;
        if (i < this.retries - 1) {
          await new Promise(r => setTimeout(r, 1000 * (i + 1)));
        }
      }
    }
    
    throw lastError;
  }
}
 
// 使用
const api = new ApiClient('https://api.example.com', {
  timeout: 5000,
  retries: 3
});
 
api.requestWithRetry('/users')
  .then(users => console.log(users))
  .catch(error => console.error(error));

场景 2:批量处理

// 批量处理数据,控制并发
async function batchProcess(items, processor, concurrency = 3) {
  const results = [];
  const executing = [];
  
  for (const item of items) {
    const promise = processor(item)
      .then(result => {
        executing.splice(executing.indexOf(promise), 1);
        return result;
      });
    
    results.push(promise);
    executing.push(promise);
    
    if (executing.length >= concurrency) {
      await Promise.race(executing);
    }
  }
  
  return Promise.all(results);
}
 
// 使用
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
 
batchProcess(items, async (item) => {
  // 处理每个项目
  await new Promise(r => setTimeout(r, 1000));
  return `处理完成:${item}`;
}, 3) // 每次处理 3 个
  .then(results => {
    console.log('全部完成:', results);
  });

8. 给新手的练习题(可立即实践)

基础题:添加错误处理

要求

  1. 创建一个 fetchData 函数
  2. 添加 try/catch 错误处理
  3. 根据错误类型显示不同的错误消息

进阶题:实现重试机制

要求

  1. 创建一个带重试的请求函数
  2. 最多重试 3 次
  3. 每次重试间隔递增(1秒、2秒、3秒)

9. 用更简单的话再总结一遍(方便复习)

异步编程最佳实践是什么

  • 就是写异步代码的”经验总结”
  • 让你写出更健壮、更高效的代码

核心要点

  1. 总是处理错误,不要忽略
  2. 独立操作要并行执行,不要串行
  3. 添加超时,避免无限等待
  4. 清理资源,避免内存泄漏
  5. 添加重试,提高成功率

记住

  • 错误处理很重要
  • 合理使用并发
  • 及时清理资源

10. 知识体系延伸 & 继续学习方向

相关知识点

继续学习方向

  1. 性能优化:学习如何优化异步代码的性能
  2. 实际项目:在真实项目中应用这些最佳实践
  3. 高级模式:学习更高级的异步编程模式
  4. 工具使用:学习使用工具来帮助异步编程

最后更新:2025

javascript 异步编程 最佳实践 前端基础