async/await 错误处理:try...catch vs .catch() 完全指南

摘要 :async/await 本质是 Promise 的语法糖,其错误处理也借鉴了同步代码的模式。try...catch 是 async/await 的「标配」------用块级语句包裹一段代码,捕获其中所有同步和异步(await)错误,一旦出错整个 try 块中断执行跳入 catch。.catch() 是 Promise 的原生链式方法------仅捕获调用它的那个 Promise 及前面链条中的错误,捕获后可返回新值让 async 函数「恢复」继续执行。核心区别在于流程控制模型:try...catch 是「中断」模型,.catch() 是「恢复」模型。选型原则:流程化、依赖性强的异步任务用 try...catch 统一处理;单个、非关键、可恢复的异步任务用 .catch() 独立处理。


一、async/await 与错误处理的关系

1.1 为什么需要专门的错误处理

async/await 让我们用同步代码的风格编写异步逻辑------代码从上到下顺序执行,不再有回调嵌套。因此它的错误处理也需要像同步代码一样直观:

js 复制代码
// 同步代码的错误处理 ------ 你已经很熟悉了
try {
  const data = JSON.parse(userInput); // 可能抛出 SyntaxError
  console.log(data.name);             // 如果上面抛错,这行不执行
} catch (error) {
  console.error('解析失败:', error.message);
}

async/await 的 try...catch 就是把这套模式直接搬到了异步事件:

js 复制代码
// 异步代码的错误处理 ------ 和同步写法一模一样!
async function fetchUser(userId) {
  try {
    const res = await fetch(`/api/users/${userId}`); // 可能网络错误
    const data = await res.json();                     // 可能 JSON 解析错误
    console.log(data.name);                            // 任一 await 失败都进 catch
    return data;
  } catch (error) {
    console.error('获取用户失败:', error.message);
    return null; // 兜底返回值
  }
}

1.2 await 的错误传播机制

await 后面的 Promise 被 reject (拒绝)时,await 表达式会 throw(抛出) 这个拒绝原因。这个抛出的错误可以被标准的 try...catch 语句捕获:

vbnet 复制代码
┌─────────────────────────────────────────────────────┐
│  await Promise.reject(new Error("失败"))            │
│         │                                          │
│         ▼ (自动 throw)                              │
│  ┌─────────────┐                                   │
│  │ try 块中断   │ ──→ 跳转到 catch(error)           │
│  │ 后续代码不执行│     error = Error("失败")        │
│  └─────────────┘                                   │
└─────────────────────────────────────────────────────┘

关键理解await 不只是「等待 Promise 完成」,它还是一个潜在的 throw 点 。每个 await 都可能成为错误的来源。


二、try...catch:async/await 的标配

2.1 核心优势

优势 说明
语法统一 可以用与同步代码完全相同的方式捕获异步和同步错误
处理多个 await 一个 try 块可以包含多个 await 表达式,任何一个失败都会被同一个 catch 捕获
包含 finally 可以像同步代码一样使用 finally 块执行清理操作(关闭加载状态、释放资源),无论成功还是失败都会执行

2.2 基础示例

还原素材中的完整代码:

js 复制代码
// 创建一个会失败的 Promise
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve("妙码-成功");
    reject(new Error("失败"));
  }, 1000);
});

// 方式一:async/await + try...catch
async function invoke() {
  try {
    const res1 = await p;  // 第一个 await: 抛出 Error("失败")
    const res2 = await p;  // 这行不会执行!(try 块已中断)

    console.log(res1, res2); // 这行也不会执行
  } catch (error) {
    console.log(error.message); // 输出: "失败"
  }
}

invoke();

执行过程分析

javascript 复制代码
时间线:
0ms      invoke() 被调用, 进入 try 块
0ms      await p → 暂停执行, 等待 p 的结果
1000ms   p reject(Error("失败"))
         → await 将 Error 作为 throw 抛出
         → try 块立即中断!
         → const res2 = await p  [跳过]
         → console.log(res1, res2) [跳过]
         → 进入 catch 块
         → 输出 "失败"

2.3 多个 await 共享一个 catch

这是 try...catch 最强大的特性------一系列相互依赖的异步操作共享同一个错误处理逻辑

js 复制代码
async function orderFlow(userId, itemId) {
  let loading = true;
  try {
    // 步骤 1: 验证用户
    const user = await verifyUser(userId);
    
    // 步骤 2: 检查库存(依赖步骤 1 的结果)
    const stock = await checkStock(itemId, user.level);
    
    // 步骤 3: 扣减库存(依赖步骤 2)
    const order = await createOrder(userId, itemId);
    
    // 步骤 4: 支付(依赖步骤 3)
    const payment = await processPayment(order.id);
    
    // 全部成功
    return { success: true, orderId: order.id };
    
  } catch (error) {
    // 任何一步失败都会到这里
    // 错误处理逻辑集中管理,代码更清晰
    console.error(`下单流程在某个环节失败:`, error.message);
    
    // 根据错误类型做不同处理
    if (error.code === 'INSUFFICIENT_STOCK') {
      return { success: false, reason: '库存不足' };
    }
    if (error.code === 'PAYMENT_DECLINED') {
      return { success: false, reason: '支付被拒' };
    }
    
    return { success: false, reason: '未知错误' };
    
  } finally {
    // 无论成功还是失败,都执行清理
    loading = false;
    hideLoadingSpinner();
  }
}

设计思想:对于构成核心业务逻辑的一系列相互依赖的异步操作,应该将它们放在同一个 try...catch 块中。这使得错误处理逻辑集中,代码更清晰。如果其中任何一步失败,整个流程都应该终止。


三、.catch():Promise 的原生恢复机制

3.1 基本用法

.catch() 是 Promise 实例上的方法,通过链式调用附加:

js 复制代码
// 方式二:Promise 链式 + .then().catch()
p.then(
  (res) => {
    console.log(res); // 成功时执行
  },
  (error) => console.log(error) // 失败时执行(第一个回调的"伴侣")
).finally(
  (res) => console.log(res) // 无论成败都执行
);

// 另一种写法:独立的 .catch()
p.then(
  (res) => {
    console.log(res);
  }
).catch(
  (error) => console.log(error)
).finally(
  () => {
    console.log("完成");
  }
);

3.2 .catch() 的「恢复」超能力

这是 .catch()try...catch 最本质的区别:

js 复制代码
async function invokeWithCatch() {
  // .catch() 捕获错误后可以返回一个新值
  // 整个 async 函数从这个错误中"恢复"过来,继续向下执行!
  const result = await p.catch((error) => {
    console.log('捕获到错误:', error.message); // 输出: "失败"
    return '默认值'; // ← 关键:返回一个新值作为"恢复值"
  });
  
  console.log('函数继续执行, result =', result); // 输出: "默认值"
  console.log('后续代码正常运行!');
  
  return result; // 返回 "默认值",不会 reject
}

invokeWithCatch();

对比两种模式

js 复制代码
// 模式 A: try...catch ------ 中断模式
async function patternA() {
  try {
    const res = await p; // 失败 → throw
    console.log('这行不执行'); // 被跳过
  } catch (e) {
    console.log(e.message);
  }
  console.log('这里正常执行'); // catch 之后继续
}

// 模式 B: .catch() ------ 恢复模式
async function patternB() {
  const res = await p.catch(() => 'fallback'); // 失败 → 恢复为 'fallback'
  console.log('这行照常执行, res =', res); // res = 'fallback'
  console.log('流程没有中断!');
}

四、try...catch vs .catch() 全面对比

4.1 特性对比表(还原素材并扩展)

维度 try...catch .catch() (Promise 方法)
语法结构 块级语句,包裹一段代码 Promise 实例方法,通过链式调用附加
作用范围 捕获 try 块内所有的同步和异步(await)错误 仅捕获调用它的那个 Promise 及其前面链条中的错误
流程控制 中断模型:错误发生时立即跳出 try 块,执行 catch 块。后续 await 不执行 恢复模型:捕获错误后可返回新值,让 async 函数继续向下执行,不会中断
代码风格 命令式、同步风格 函数式、链式风格
finally 支持 try {} catch(e) {} finally {} .then().catch().finally()
多错误统一处理 ✅ 一个 catch 处理所有 await ❌ 每个 Promise 需要各自 .catch()
精确错误定位 ❌ 不知道哪个 await 出错(需额外判断) ✅ 精确知道哪个 Promise 失败
混合同步/异步 ✅ 同时捕获同步错误(如 JSON.parse)和异步错误 ❌ 只能捕获 Promise 错误,同步错误需额外处理
嵌套复杂度 深层嵌套时 try 层级变多 链式扁平,但过长时可读性下降

4.2 流程控制的核心区别(重点!)

这是面试最高频考点:

dart 复制代码
┌── try...catch: 中断模型 ──────────────────────────────┐
│                                                        │
│   try {                                                │
│     await step1();  ✅                                 │
│     await step2();  ❌ throw!                          │
│     await step3();  ⛔ 跳过! (try 块已中断)             │
│   }                                                    │
│   catch(e) { ... }  ← 在此处理错误                     │
│   // catch 之后的代码正常执行                           │
│                                                        │
└────────────────────────────────────────────────────────┘

┌── .catch(): 恢复模型 ─────────────────────────────────┐
│                                                        │
│   const r1 = await step1().catch(e => fallback1);  ✅  │
│   const r2 = await step2().catch(e => fallback2);  ✅  │
│   const r3 = await step3().catch(e => fallback3);  ✅  │
│   // 即使 step2 失败, step3 照常执行!                   │
│   // 每个步骤独立容灾, 互不影响                         │
│                                                        │
└────────────────────────────────────────────────────────┘

五、实战场景与选型指南

5.1 场景一:核心业务流程 --- 首选 try...catch

当一系列异步操作相互依赖、构成完整事务时,用 try...catch:

js 复制代码
// 电商下单完整流程:任一步失败则整体回滚
async function placeOrder(orderData) {
  const transactionId = generateId();
  
  try {
    // 1. 锁定库存
    await lockInventory(orderData.items);
    
    // 2. 创建订单
    const order = await createOrderInDB(orderData, transactionId);
    
    // 3. 扣款
    await chargePayment(orderData.payment, order.total);
    
    // 4. 发送确认通知
    await sendConfirmationEmail(orderData.email, order.id);
    
    // 全部成功 → 提交事务
    await commitTransaction(transactionId);
    return { status: 'success', order };
    
  } catch (error) {
    // 任何步骤失败 → 回滚整个事务
    await rollbackTransaction(transactionId);
    
    // 记录错误日志
    await logError({
      transactionId,
      step: 'placeOrder',
      error: error.message,
      timestamp: Date.now()
    });
    
    // 通知用户
    showErrorMessage('下单失败,请稍后重试');
    
    return { status: 'failed', reason: error.message };
  }
}

为什么不用 .catch()? 因为步骤 2~4 都依赖前一步的结果。如果步骤 2 失败了,步骤 3 和 4 根本不应该执行。try...catch 的「中断」语义完美匹配这种需求。

5.2 场景二:非关键独立任务 --- 用 .catch()

当一个异步操作非关键、或其失败不应中断整个函数流程 时,用行内的 .catch()

js 复制代码
async function loadHomePage(userId) {
  // 关键数据:必须成功,用 try...catch
  let userData;
  try {
    userData = await fetchUserData(userId);
  } catch (e) {
    redirectToLogin();
    return; // 关键数据失败,直接终止
  }
  
  // 主要内容:必须成功
  const articles = await fetchArticles().catch(() => []);
  if (!articles.length) {
    showEmptyState();
  }
  
  // ====== 下面是非关键内容 ======
  
  // 广告数据:失败了没关系,页面照样展示
  const ads = await fetchAds().catch(() => []); 
  renderAds(ads);
  
  // 推荐内容:失败了显示默认推荐
  const recommendations = await fetchRecommendations(userId)
    .catch(() => getDefaultRecommendations());
  renderRecommendations(recommendations);
  
  // 用户行为埋点:完全不应影响用户体验
  trackPageView('/home').catch(() => {}); // 静默失败
  
  // A/B 测试配置:取不到就用默认值
  const abConfig = await fetchABConfig()
    .catch(() => ({ showBanner: true, theme: 'light' }));
  applyTheme(abConfig.theme);
  
  return { userData, articles, ads, recommendations };
}

为什么这里用 .catch()? 广告加载失败不应该导致整个白屏;埋点失败更不能影响用户体验。每个非关键操作独立容灾,互不影响。

5.3 场景三:两者混用的最佳实践

实际项目中,最优雅的方式是组合使用

js 复制代码
class UserService {
  async getUserProfile(userId) {
    // 外层:核心流程用 try...catch 保证原子性
    try {
      // 并行请求无依赖的数据
      const [basicInfo, permissions, preferences] = await Promise.all([
        this.fetchBasicInfo(userId),
        this.fetchPermissions(userId),
        // 非关键数据:单独 .catch() 提供默认值
        this.fetchPreferences(userId).catch(() => ({
          theme: 'light',
          language: 'zh-CN',
          notifications: true
        }))
      ]);
      
      // 有依赖的后续操作继续在 try 内
      const enrichedData = await this.enrichUserData(basicInfo, permissions);
      
      return { ...enrichedData, preferences };
      
    } catch (error) {
      // 统一的错误处理 + 上报
      reportError(error, { userId, context: 'getUserProfile' });
      
      // 根据错误类型提供不同的降级策略
      if (error.status === 401) {
        throw new AuthError('登录已过期'); // 向上抛出让调用方处理
      }
      if (error.status === 403) {
        return getGuestProfile(); // 权限不足 → 降级为游客模式
      }
      
      // 其他未知错误 → 返回最小可用数据
      return getMinimalProfile();
    }
  }
}

六、高级技巧与常见陷阱

6.1 技巧一:带分类的 catch 处理

js 复制代码
class AppError extends Error {
  constructor(message, code, statusCode) {
    super(message);
    this.code = code;
    this.statusCode = statusCode;
  }
}

class NetworkError extends AppError {
  constructor(originalError) {
    super(originalError.message, 'NETWORK_ERROR', 503);
    this.originalError = originalError;
  }
}

class ValidationError extends AppError {
  constructor(fields) {
    super('参数校验失败', 'VALIDATION_ERROR', 400);
    this.fields = fields;
  }
}

// 使用
async function submitForm(formData) {
  try {
    validate(formData); // 可能抛 ValidationError
    const result = await api.submit(formData); // 可能抛 NetworkError
    return result;
  } catch (error) {
    if (error instanceof NetworkError) {
      showRetryButton(); // 网络错误 → 提示重试
    } else if (error instanceof ValidationError) {
      highlightFields(error.fields); // 校验错误 → 高亮字段
    } else {
      showErrorDialog(error.message); // 其他错误 → 通用提示
    }
    throw error; // 可以选择继续向上抛出
  }
}

6.2 技巧二:async 函数内的同步错误也会被 catch

js 复制代码
async function demo() {
  try {
    const data = JSON.parse('{ invalid json'); // 同步 SyntaxError!
    const res = await fetch('/api/data');       // 这行不执行
  } catch (error) {
    // 同时捕获了同步错误和异步错误!
    console.log(error instanceof SyntaxError); // true
  }
}

这是 try...catch 相比 .catch() 的独特优势------同时覆盖同步和异步两类错误

6.3 陷阱一:未捕获的 Promise rejection

js 复制代码
// 危险! 如果 forgetToAwait() 内部的 Promise 失败,
// 错误不会被外层 try...catch 捕获!
async function dangerous() {
  try {
    forgetToAwait(); // 没有 await! Promise 在"后台"运行
    console.log('继续执行...');
  } catch (e) {
    // 永远不会到达这里!
  }
}

function forgetToAwait() {
  return Promise.reject(new Error('未捕获'));
}

// 正确写法
async function safe() {
  try {
    await forgetToAwait(); // 加上 await!
  } catch (e) {
    console.log('正确捕获:', e.message); // "未捕获"
  }
}

6.4 陷阱二:forEach 中的 async/await 无效

js 复制代码
// 坑! forEach 不支持 async callback
const ids = [1, 2, 3];
ids.forEach(async (id) => {
  try {
    const data = await fetchData(id);
    console.log(data);
  } catch (e) {
    console.error(e); // 虽然 catch 能工作...
  }
});
// 但外层无法等待所有 forEach 完成!

// 正确: for...of 或 for 循环
for (const id of ids) {
  try {
    const data = await fetchData(id);
    console.log(data);
  } catch (e) {
    console.error(e);
  }
}
// 或: map + Promise.all
await Promise.all(ids.map(async (id) => {
  try {
    return await fetchData(id);
  } catch (e) {
    return null; // 单个失败不影响其他
  }
}));

6.5 陷阱三:catch 中再次 throw

js 复制代码
async function rethrowDemo() {
  try {
    await riskyOperation();
  } catch (error) {
    console.log('第一层捕获:', error.message);
    
    // 选择 1: 处理后不再抛出 → 函数正常返回
    // return defaultValue;
    
    // 选择 2: 包装后重新抛出 → 让上层处理
    throw new Error(`操作失败: ${error.message}`, { cause: error });
    
    // 选择 3: 直接原样抛出
    // throw error;
  }
}

// 调用方
try {
  await rethrowDemo();
} catch (e) {
  console.log('第二层捕获:', e.message); // "操作失败: ..."
  console.log('原始错误:', e.cause?.message);  // ES2022 Error.cause
}

6.6 技巧四:高阶封装 --- withRetry

js 复制代码
// 自动重试装饰器
async function withRetry(fn, options = {}) {
  const {
    retries = 3,
    delay = 1000,
    backoff = 2, // 指数退避倍数
    shouldRetry = (e) => e.statusCode >= 500, // 仅服务端错误重试
  } = options;
  
  let lastError;
  
  for (let i = 0; i <= retries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      
      if (i === retries || !shouldRetry(error)) {
        throw error; // 重试耗尽或不可重试 → 最终抛出
      }
      
      const waitTime = delay * Math.pow(backoff, i);
      console.log(`第 ${i + 1} 次重试, ${waitTime}ms 后执行...`);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
  }
  
  throw lastError;
}

// 使用
const data = await withRetry(
  () => fetch('/api/expensive-data').then(r => r.json()),
  { retries: 3, delay: 1000 }
);

七、完整流程串联

下面用一个综合示例走通全部知识点:

js 复制代码
/**
 * 场景:电商首页数据加载
 * 要求:
 *   - 用户信息是关键的,失败则跳转登录
 *   - 商品列表是主要的,失败显示空状态
 *   - 广告/推荐/埋点是非关键的,失败静默降级
 */

async function loadEcommerceHome(userId) {
  // ══════════════════════════════════════
  // 第一层:关键数据 --- try...catch 统一处理
  // ══════════════════════════════════════
  let user;
  try {
    // 同步校验也可能出错(try...catch 同时捕获)
    if (!userId || typeof userId !== 'string') {
      throw new ValidationError(['userId'], '无效的用户 ID');
    }
    
    // 核心 API:必须成功
    user = await withRetry(
      () => fetch(`/api/users/${userId}`).then(r => {
        if (!r.ok) throw new AppError(r.statusText, 'API_ERROR', r.status);
        return r.json();
      }),
      { retries: 2 }
    );
    
  } catch (error) {
    // 分类处理
    if (error instanceof ValidationError) {
      showToast('参数错误,请刷新页面重试');
    } else if (error?.statusCode === 401) {
      redirectToLogin(); // 未授权 → 跳转登录
      return;           // 终止整个函数
    } else {
      showToast('网络异常,请检查连接');
      sendErrorReport(error); // 错误上报
    }
    user = getGuestUser(); // 降级为游客
  }

  // ══════════════════════════════════════
  // 第二层:主要数据 --- try...catch + 降级
  // ══════════════════════════════════════
  let products = [];
  try {
    products = await fetchProducts({ category: 'featured' });
  } catch (e) {
    products = []; // 空列表降级
    showEmptyProductsPlaceholder();
  }

  // ══════════════════════════════════════
  // 第三层:非关键数据 --- 各自 .catch() 独立容灾
  // ══════════════════════════════════════
  
  // 广告:失败用空数组
  const banners = await fetchBanners()
    .catch(() => []);
  renderBanners(banners);
  
  // 个性化推荐:失败用热门商品替代
  const recommendations = await fetchRecommendations(user.tags)
    .catch(() => getTrendingProducts());
  renderRecommendations(recommendations);
  
  // 购物车数量:失败显示 0
  const cartCount = await fetchCartCount(userId)
    .catch(() => 0);
  updateCartBadge(cartCount);
  
  // 行为埋点:纯副作用,失败完全忽略
  trackPageView('home', { userId })
    .catch(() => {}); // 静默吞掉
  
  // ══════════════════════════════════════
  // 清理(无论成败都执行)
  // ══════════════════════════════════════
  hideGlobalLoader();
  
  return { user, products, banners, recommendations, cartCount };
}

// 调用
loadEcommerceHome('user_123')
  .then(data => console.log('首页加载完成', data))
  .catch(err => console.error('致命错误(未被内部捕获)', err));

执行流程图

ini 复制代码
loadEcommerceHome('user_123')
  │
  ├─[try] 校验 userId + fetch 用户信息
  │    ├─ 成功 → user = 数据
  │    └─ 失败 → catch → 分类处理 / 降级 guest / 或 return 终止
  │
  ├─[try] fetch 商品列表
  │    ├─ 成功 → products = 数据
  │    └─ 失败 → catch → products = [] + 显示空状态占位
  │
  ├─[.catch()] fetch 广告 → 失败则 banners = []
  ├─[.catch()] fetch 推荐 → 失败则用热门替代
  ├─[.catch()] fetch 购物车 → 失败则 cartCount = 0
  ├─[.catch()] trackPageView → 失败则静默忽略
  │
  ├─[finally] hideGlobalLoader()
  │
  └─ return { user, products, banners, recommendations, cartCount }

八、速查表与记忆口诀

8.1 选型决策树

dart 复制代码
这个异步操作失败后需要怎样?
  │
  ├── 整个流程都应该停止?
  │     └── ✅ 用 try...catch 包裹整段逻辑
  │
  ├── 可以用默认值/降级方案代替?
  │     └── ✅ 用行内 .catch(() => defaultValue)
  │
  ├── 需要知道具体是哪步失败?
  │     └── ✅ 每个 await 单独 .catch() 或分多个 try 块
  │
  └── 需要做清理操作(无论成败)?
        └── ✅ try 的 finally 或 .finally()

8.2 记忆口诀

try 包裹依赖流,一错全停进 catch

catch 链式能恢复,返回新值继续走

关键数据用 try,非关键用 catch

同步异步都能抓,finally 清理忘不了

8.3 一句话总结

  • try...catch = async/await 的「标配」,用于处理流程化、依赖性强 的异步任务,提供集中式的错误处理
  • .catch() = Promise 的原生方法 ,在 async/await 中可用于处理单个、非关键、可恢复 的异步任务,实现更精细的流程控制

九、面试高频 Q&A

Q1: try...catch 和 .catch() 本质区别是什么?

A: 流程控制模型不同。try...catch 是「中断」模型------一旦出错,整个 try 块剩余代码全部跳过。.catch() 是「恢复」模型------捕获错误后可返回新值,让 async 函数继续执行。

Q2: 什么时候该用 try...catch?什么时候用 .catch()?

A: 判断标准------如果失败后后续步骤不应该执行 (如依赖上一步结果的业务流程),用 try...catch;如果失败后可以用默认值替代且不影响其他操作(如广告/埋点/推荐),用 .catch()。

Q3: try...catch 能捕获同步错误吗?

A: 能!这是它相比 .catch() 的独特优势。JSON.parse 抛出的 SyntaxError、手动 throw 的错误、甚至 TypeError(如 null.property),只要在 try 块内发生,都会被 catch 捕获。

Q4: async 函数中忘记 await 一个 reject 的 Promise 会怎样?

A: 它变成一个「未捕获的 Promise rejection」,不会被外层的 try...catch 捕获。在浏览器中会在控制台警告,在 Node.js 中可能导致进程退出(取决于版本)。解决方式:始终 await 或 .catch() 每个 Promise。

Q5: .catch() 里面再 throw 会怎样?

A: 错误会继续向上冒泡。可以在 catch 中做「错误包装」(如添加上下文信息后重新 throw),让上层调用方处理。ES2022 支持 new Error(msg, { cause: originalError }) 保持错误链。

相关推荐
Scabbards_1 小时前
面试Leetcode - 算法合集
算法·leetcode·面试
半个落月1 小时前
React 性能优化入门:用 memo 避免无关的子组件重复渲染
前端·react.js
北城笑笑1 小时前
Server 18 ,Nginx + Vite 前端部署排错实战:从 `/api/gpt/chat` 404 到 9012 后端服务的完整定位过程
linux·运维·前端·nginx·ubuntu·vue
江华森1 小时前
从抓包到嗅探:用 C 语言把《计算机网络》第 9-13 章全部跑一遍
前端
IT_陈寒2 小时前
Vite打包给我挖的这个坑,差点搞崩我的项目
前端·人工智能·后端
恋猫de小郭2 小时前
Flutter A2UI 的正确用法,怎么把 AI 和动态 UI 结合有效生产
android·前端·flutter
程序员-珍2 小时前
安卓开发:同名 styles.xml 在不同分支/文件夹的合并规则
android·xml·前端
Ashley的成长之路2 小时前
前端性能优化实战手册·第5篇(最终篇):性能监控与 CI/CD 集成
前端·ci/cd·性能优化
chemddd2 小时前
第四节课:前端
前端