你的 Promise.then 可能白写了,新语法让异步代码更优雅

先看这段代码,你是不是也这样写过?

// 经典的 Promise.then 链式调用
fetch('/api/user/1')
  .then(response => response.json())
  .then(user => {
    return fetch(`/api/posts?userId=${user.id}`);
  })
  .then(response => response.json())
  .then(posts => {
    return fetch(`/api/comments?postId=${posts[0].id}`);
  })
  .then(response => response.json())
  .then(comments => {
    console.log('用户的第一篇文章的评论:', comments);
  })
  .catch(error => {
    console.error('出错了:', error);
  })
  .finally(() => {
    console.log('请求结束');
  });

这种代码被称为"回调地狱的现代版"——Promise 地狱。虽然比传统回调好一些,但依然有这几个问题:

  1. 可读性差:需要一层层理解每个 .then 在做什么
  2. 错误处理麻烦:一个 .catch 处理所有错误,难以定位问题
  3. 调试困难:断点设置在哪里?错误发生在哪一步?
  4. 代码冗长:为了几行逻辑,写了十几行代码

今天我要告诉你:你完全可以写出更优雅的异步代码


Promise.then 的 3 大痛点

1. 错误处理不精确

// ❌ 一个 catch 处理所有错误
fetch('/api/user')
  .then(response => response.json())
  .then(user => updateUI(user))
  .then(() => fetch('/api/posts'))
  .then(response => response.json())
  .then(posts => renderPosts(posts))
  .catch(error => {
    // 哪个步骤出错了?不知道!
    console.error('出错了:', error);
  });

// 实际开发中,我们经常需要:
// 1. 记录哪个步骤出错
// 2. 某些错误可以继续执行
// 3. 给用户不同的错误提示

2. 无法同时执行多个异步操作

// ❌ 顺序执行,效率低下
fetch('/api/user')
  .then(user => {
    return fetch('/api/posts');  // 等用户信息返回后再请求文章
  })
  .then(posts => {
    return fetch('/api/comments'); // 等文章返回后再请求评论
  });
// 总共耗时 = 三个请求时间之和

// 这三个请求明明可以同时发出!

3. 流程控制困难

// ❌ 复杂的条件判断让代码混乱
let userData;

fetch('/api/user')
  .then(user => {
    userData = user;
    if (user.role === 'admin') {
      return fetch('/api/admin-data');
    } else {
      return fetch('/api/user-data');
    }
  })
  .then(data => {
    // 这里需要知道 data 是 admin-data 还是 user-data
    // 还需要访问 userData 变量
    return processData(userData, data);
  })
  .then(result => {
    // 更复杂的分支...
  });
// 变量在 Promise 链外部声明,破坏了封装性

新方案:async/await + 现代 Promise API

方案1:async/await - 让异步代码像同步一样

// ✅ 用 async/await 重写上面的例子
async function getUserComments() {
  try {
    // 1. 获取用户信息
    const userResponse = await fetch('/api/user/1');
    const user = await userResponse.json();
    
    // 2. 获取用户的文章
    const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
    const posts = await postsResponse.json();
    
    // 3. 获取第一篇文章的评论
    const commentsResponse = await fetch(`/api/comments?postId=${posts[0].id}`);
    const comments = await commentsResponse.json();
    
    console.log('用户的第一篇文章的评论:', comments);
    return comments;
  } catch (error) {
    console.error('出错了:', error);
    throw error;
  } finally {
    console.log('请求结束');
  }
}

// 使用
getUserComments();

对比一下

  • 之前:7 个 .then,嵌套 4 层,需要横向滚动阅读
  • 之后:顺序执行,清晰明了,像写同步代码一样

方案2:并行执行 - 用 Promise.all 加速

// ❌ 顺序执行(慢)
async function fetchDataSequentially() {
  const user = await fetch('/api/user');      // 等待 100ms
  const posts = await fetch('/api/posts');    // 等待 200ms
  const comments = await fetch('/api/comments'); // 等待 150ms
  // 总时间:100 + 200 + 150 = 450ms
}

// ✅ 并行执行(快)
async function fetchDataParallel() {
  // 三个请求同时发出
  const [user, posts, comments] = await Promise.all([
    fetch('/api/user').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/comments').then(r => r.json())
  ]);
  // 总时间:max(100, 200, 150) = 200ms
  // 快了 125%!
  
  return { user, posts, comments };
}

方案3:更灵活的并发控制

// ✅ Promise.allSettled - 所有请求都完成,不管成功失败
async function fetchMultipleData(urls) {
  const promises = urls.map(url => fetch(url).then(r => r.json()));
  
  const results = await Promise.allSettled(promises);
  
  // 分类处理结果
  const successful = results.filter(r => r.status === 'fulfilled');
  const failed = results.filter(r => r.status === 'rejected');
  
  console.log(`成功: ${successful.length}, 失败: ${failed.length}`);
  
  return {
    data: successful.map(r => r.value),
    errors: failed.map(r => r.reason)
  };
}

// ✅ Promise.race - 哪个先完成就用哪个(超时控制)
async function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url).then(r => r.json());
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('请求超时')), timeout);
  });
  
  // 谁先完成就用谁的结果
  return await Promise.race([fetchPromise, timeoutPromise]);
}

// ✅ Promise.any - 只要有一个成功就返回(类似竞速)
async function fetchFromFastestServer(servers) {
  const promises = servers.map(server => 
    fetch(server).then(r => r.json())
  );
  
  try {
    // 只要有一个成功就返回,忽略所有失败
    const result = await Promise.any(promises);
    return result;
  } catch (error) {
    // 所有请求都失败了
    console.error('所有服务器都失败了:', error.errors);
    throw error;
  }
}

实战:重构真实业务代码

场景1:电商购物车结算流程

// ❌ Promise.then 版本(混乱)
function checkoutOld(userId, cartItems) {
  let userInfo, inventoryStatus, shippingOptions;
  
  getUser(userId)
    .then(user => {
      userInfo = user;
      return checkInventory(cartItems);
    })
    .then(inventory => {
      inventoryStatus = inventory;
      return calculateShipping(userInfo.address, cartItems);
    })
    .then(shipping => {
      shippingOptions = shipping;
      return calculateTotal(cartItems, shippingOptions);
    })
    .then(total => {
      return processPayment(userInfo.paymentMethod, total);
    })
    .then(paymentResult => {
      return createOrder(userInfo, cartItems, inventoryStatus, shippingOptions, paymentResult);
    })
    .then(order => {
      sendConfirmationEmail(userInfo.email, order);
      return order;
    })
    .catch(error => {
      console.error('结算失败:', error);
      // 哪个步骤出错了?很难知道!
    });
}

// ✅ async/await 版本(清晰)
async function checkoutNew(userId, cartItems) {
  try {
    // 1. 获取用户信息
    const user = await getUser(userId);
    
    // 2. 并行检查库存和计算运费(提高速度)
    const [inventory, shippingOptions] = await Promise.all([
      checkInventory(cartItems),
      calculateShipping(user.address, cartItems)
    ]);
    
    // 3. 如果有缺货商品,提前返回
    const outOfStockItems = inventory.filter(item => !item.inStock);
    if (outOfStockItems.length > 0) {
      throw new Error(`以下商品缺货: ${outOfStockItems.map(i => i.name).join(', ')}`);
    }
    
    // 4. 计算总价并处理支付
    const total = calculateTotal(cartItems, shippingOptions);
    const paymentResult = await processPayment(user.paymentMethod, total);
    
    // 5. 创建订单
    const order = await createOrder(user, cartItems, inventory, shippingOptions, paymentResult);
    
    // 6. 发送确认邮件(不阻塞主流程)
    sendConfirmationEmail(user.email, order).catch(emailError => {
      console.warn('邮件发送失败,但不影响订单:', emailError);
    });
    
    return order;
    
  } catch (error) {
    // 精确的错误处理
    if (error.message.includes('缺货')) {
      console.error('库存问题:', error.message);
      throw new Error('CART_OUT_OF_STOCK');
    } else if (error.message.includes('支付失败')) {
      console.error('支付问题:', error.message);
      throw new Error('PAYMENT_FAILED');
    } else {
      console.error('结算流程出错:', error);
      throw new Error('CHECKOUT_FAILED');
    }
  }
}

场景2:分页加载数据

// ❌ 传统方式:一次加载所有页
async function loadAllPagesBadly(totalPages) {
  const allData = [];
  
  for (let page = 1; page <= totalPages; page++) {
    const data = await fetch(`/api/data?page=${page}`).then(r => r.json());
    allData.push(...data.items);
  }
  
  return allData;
  // 问题:顺序执行,很慢!
  // 100页 × 200ms = 20秒!
}

// ✅ 现代方式:并发控制加载
async function loadAllPagesSmartly(totalPages, concurrency = 5) {
  const allData = [];
  
  // 创建所有页面的请求
  const pagePromises = [];
  for (let page = 1; page <= totalPages; page++) {
    pagePromises.push(
      fetch(`/api/data?page=${page}`).then(r => r.json())
    );
  }
  
  // 分批并发执行
  for (let i = 0; i < pagePromises.length; i += concurrency) {
    const batch = pagePromises.slice(i, i + concurrency);
    const batchResults = await Promise.all(batch);
    
    // 收集数据
    batchResults.forEach(result => {
      allData.push(...result.items);
    });
    
    // 显示进度
    const progress = Math.min(i + concurrency, pagePromises.length);
    console.log(`加载进度: ${progress}/${pagePromises.length}`);
  }
  
  return allData;
  // 优点:5个请求并发,100页只需要 20 批
  // 100页 ÷ 5并发 = 20批 × 200ms = 4秒!
}

// ✅ 更优雅的版本:使用 async 池
async function asyncPool(poolLimit, array, iteratorFn) {
  const ret = [];
  const executing = [];
  
  for (const item of array) {
    const p = Promise.resolve().then(() => iteratorFn(item));
    ret.push(p);
    
    if (poolLimit <= array.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e);
      
      if (executing.length >= poolLimit) {
        await Promise.race(executing);
      }
    }
  }
  
  return Promise.all(ret);
}

// 使用 async 池加载
async function loadWithPool(totalPages, poolSize = 5) {
  const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
  
  const results = await asyncPool(poolSize, pages, async (page) => {
    const response = await fetch(`/api/data?page=${page}`);
    return response.json();
  });
  
  return results.flatMap(r => r.items);
}

场景3:带重试机制的请求

// ❌ 传统的重试逻辑(复杂)
function fetchWithRetryOld(url, retries = 3) {
  return fetch(url)
    .then(response => {
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response.json();
    })
    .catch(error => {
      if (retries > 0) {
        console.log(`重试剩余次数: ${retries}`);
        return fetchWithRetryOld(url, retries - 1);
      } else {
        throw error;
      }
    });
}

// ✅ 现代的 async/await 重试(清晰)
async function fetchWithRetry(url, retries = 3, delay = 1000) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(url);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      return await response.json();
      
    } catch (error) {
      console.log(`请求失败 (尝试 ${attempt}/${retries}):`, error.message);
      
      if (attempt === retries) {
        throw new Error(`请求失败,已重试 ${retries} 次: ${error.message}`);
      }
      
      // 指数退避策略
      const waitTime = delay * Math.pow(2, attempt - 1);
      console.log(`等待 ${waitTime}ms 后重试...`);
      await sleep(waitTime);
    }
  }
}

// ✅ 更高级的:带超时和重试的组合
async function robustFetch(url, options = {}) {
  const { timeout = 10000, retries = 3, retryDelay = 1000 } = options;
  
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      // 创建超时控制器
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 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) {
      console.log(`请求失败 (尝试 ${attempt}/${retries}):`, error.name, error.message);
      
      if (attempt === retries) {
        throw new Error(`请求最终失败: ${error.message}`);
      }
      
      // 如果是中止错误(超时),立即重试
      if (error.name === 'AbortError') {
        console.log('请求超时,立即重试...');
      } else {
        // 其他错误,使用指数退避
        const waitTime = retryDelay * Math.pow(2, attempt - 1);
        await sleep(waitTime);
      }
    }
  }
}

// 辅助函数:等待指定时间
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

高级技巧:让异步代码更强大

1. 错误边界模式

// 创建错误边界,让部分错误不影响整体
async function withErrorBoundary(promise, fallbackValue = null) {
  try {
    return await promise;
  } catch (error) {
    console.error('操作失败,使用回退值:', error.message);
    return fallbackValue;
  }
}

// 使用
async function loadDashboard() {
  const [user, notifications, recommendations] = await Promise.all([
    withErrorBoundary(fetchUser(), { name: '访客' }),
    withErrorBoundary(fetchNotifications(), []),
    withErrorBoundary(fetchRecommendations(), [])
  ]);
  
  // 即使某个请求失败,其他数据也能正常显示
  return { user, notifications, recommendations };
}

2. 进度追踪

// 追踪多个异步操作的进度
async function withProgress(tasks, onProgress) {
  let completed = 0;
  const total = tasks.length;
  
  // 包装每个任务,完成后更新进度
  const wrappedTasks = tasks.map(task => async () => {
    const result = await task();
    completed++;
    
    if (onProgress) {
      onProgress({
        completed,
        total,
        percent: Math.round((completed / total) * 100)
      });
    }
    
    return result;
  });
  
  // 并行执行所有任务
  const results = await Promise.all(wrappedTasks.map(t => t()));
  return results;
}

// 使用
const tasks = [
  () => fetch('/api/data1').then(r => r.json()),
  () => fetch('/api/data2').then(r => r.json()),
  () => fetch('/api/data3').then(r => r.json())
];

const results = await withProgress(tasks, ({ percent }) => {
  console.log(`加载进度: ${percent}%`);
});

3. 竞态条件防护

// 防止多次点击导致的重复请求
function createRequestLock() {
  let currentRequest = null;
  
  return async function(requestFn) {
    // 如果已经有请求在进行,返回同一个 Promise
    if (currentRequest) {
      console.log('请求已在进行中,等待结果...');
      return currentRequest;
    }
    
    try {
      currentRequest = requestFn();
      return await currentRequest;
    } finally {
      currentRequest = null;
    }
  };
}

// 使用
const lockedFetch = createRequestLock();

// 多次快速调用只会执行一次
button.addEventListener('click', async () => {
  const data = await lockedFetch(() => 
    fetch('/api/data').then(r => r.json())
  );
  console.log('数据:', data);
});

什么时候还需要 Promise.then?

虽然 async/await 很好,但在某些场景下,.then() 仍然有用:

1. 简单的链式调用

// 当只有一两个步骤时,.then() 更简洁
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));

// 如果用 async/await,需要多写几行
async function fetchData() {
  const response = await fetch('/api/data');
  const data = await response.json();
  console.log(data);
}

2. 需要立即执行,不等待

// 使用 .then() 可以立即返回,不阻塞
function fetchDataAndDoSomething() {
  // 立即返回 Promise,不等待
  return fetch('/api/data')
    .then(response => response.json())
    .then(data => {
      // 这里会在数据返回后执行
      processData(data);
      return data;
    });
  
  // 后面的代码会立即执行
  console.log('请求已发出,继续执行其他代码...');
}

3. 在非 async 函数中处理 Promise

// 在类方法或构造函数中
class DataLoader {
  constructor() {
    this.data = null;
    
    // 不能在构造函数中用 await
    this.loadData()
      .then(data => {
        this.data = data;
      })
      .catch(error => {
        console.error('加载失败:', error);
      });
  }
  
  loadData() {
    return fetch('/api/data').then(r => r.json());
  }
}

性能对比

可读性对比

// .then() 链
fetchA()
  .then(a => fetchB(a))
  .then(b => fetchC(b))
  .then(c => fetchD(c))
  .then(d => console.log(d))
  .catch(e => console.error(e));

// async/await
try {
  const a = await fetchA();
  const b = await fetchB(a);
  const c = await fetchC(b);
  const d = await fetchD(c);
  console.log(d);
} catch (e) {
  console.error(e);
}

// 明显后者更易读

错误处理对比

// .then() 的错误处理分散
fetchA()
  .then(a => {
    return fetchB(a).catch(e => ({ error: 'B失败', fallback: true }));
  })
  .then(b => {
    if (b.error) {
      return fetchC2();
    }
    return fetchC(b);
  })
  .catch(e => console.error('最终错误:', e));

// async/await 的错误处理集中
try {
  const a = await fetchA();
  
  let b;
  try {
    b = await fetchB(a);
  } catch {
    b = { fallback: true };
  }
  
  const c = b.fallback ? await fetchC2() : await fetchC(b);
} catch (e) {
  console.error('错误:', e);
}

迁移指南:从 Promise.then 到 async/await

步骤1:识别可以简化的 Promise 链

// 查找多层 .then()
grep -r "\.then.*\.then" src/ --include="*.js" --include="*.ts"

步骤2:逐步重构

// 之前
function getUserData(userId) {
  return fetch(`/api/users/${userId}`)
    .then(response => response.json())
    .then(user => {
      return fetch(`/api/posts?userId=${user.id}`);
    })
    .then(response => response.json())
    .then(posts => {
      return { user: userId, posts };
    });
}

// 之后
async function getUserData(userId) {
  const userResponse = await fetch(`/api/users/${userId}`);
  const user = await userResponse.json();
  
  const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
  const posts = await postsResponse.json();
  
  return { user: userId, posts };
}

步骤3:并行优化

// 识别可以并行的操作
// 之前
async function getDashboardData() {
  const user = await getUser();
  const notifications = await getNotifications(); // 等待用户数据后才执行
  const messages = await getMessages(); // 等待通知后才执行
  
  return { user, notifications, messages };
}

// 之后
async function getDashboardData() {
  const [user, notifications, messages] = await Promise.all([
    getUser(),
    getNotifications(),
    getMessages()
  ]);
  
  return { user, notifications, messages };
}

最后的建议

记住这些最佳实践:

  1. 默认使用 async/await:让代码更清晰
  2. 并行能用就用:用 Promise.all 加速
  3. 错误处理要具体:不要一个 catch 抓所有
  4. 适当使用现代 APIPromise.allSettledPromise.any

今天就可以开始的 3 件事:

  1. 找一个复杂的 Promise 链重写

    // 选一个多层 .then() 的函数
    // 用 async/await 重写,体验清晰度的提升
    
  2. 检查项目中的顺序请求

    // 找出可以并行执行的请求
    // 用 Promise.all 改造,体验速度的提升
    
  3. 统一错误处理模式

    // 创建一个错误处理工具函数
    // 让所有异步操作都有统一的错误处理
    

记住这个进化路线:

回调地狱 → Promise.then → async/await → 现代并发模式

从今天开始,告别冗长的 .then() 链。你会发现,异步代码不仅可以很强大,还可以很优雅。

好的异步代码应该像流水一样自然,而不是像迷宫一样复杂。 用对工具,你也能写出这样的代码。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐