ReactNative异步模式Skill rn-async-patterns

这篇文章提供了在 React Native 和 Zustand 中正确使用异步/等待模式的指南,包括如何识别和修复异步代码中的常见问题,例如悬浮承诺、竞态条件和异步操作的顺序问题。

移动开发 0 次安装 0 次浏览 更新于 3/3/2026

name: rn-async-patterns description: Async/await 正确性在 React Native 中使用 Zustand。当调试竞态条件、缺失 awaits、悬浮承诺或在 Expo/React Native 应用中的异步定时问题时使用。

React Native 异步模式

问题陈述

React Native 中的异步错误是隐蔽的,因为它们通常在开发中可以工作,但在负载下或在较慢的设备上失败。最常见的问题:缺失对异步函数的 await、状态更新之间的竞态条件,以及假设操作按顺序完成。


模式:悬浮承诺检测

问题: 调用异步函数而不使用 await 会导致它在后台运行。如果后续代码依赖于其完成,您将获得竞态条件。

示例(来自重拍错误):

// 之前(有缺陷)- enableSkillAreaRetake 是异步的但没有被等待
enableSkillAreaRetake(skillArea);         // 先开火后忘记 ❌
await clearSkillAreaAnswers(skillArea);   // 在 enable 完成前运行

// 之后(已修复)
await enableSkillAreaRetake(skillArea);   // 等待状态更新 ✅
await clearSkillAreaAnswers(skillArea);   // 现在按正确的顺序运行

为什么它是微妙的: 两个函数可能在其签名中都有 async,但只有一个被等待了。代码乍一看“看起来是对的”。

检测:

# 查找潜在的悬浮承诺 - 没有 await 的异步调用
grep -rn "^\s*[a-zA-Z]*\s*(" --include="*.ts" --include="*.tsx" | \
  grep -v "await\|return\|const\|let\|if\|else\|=>"

预防:

  1. ESLint 规则 @typescript-eslint/no-floating-promises - 在 lint 时间捕获这个问题
  2. 代码审查触发器:任何调用可能异步的函数的行,没有 awaitreturn 或赋值

模式:后置条件验证

问题: 假设异步调用成功而没有验证。调用可能提前返回,默默抛出,或未能更新状态。

示例(来自重拍错误):

// 之前(有缺陷)- 假设加载工作了
await loadCompletedAssessmentAnswers(id);
// 盲目继续重拍流程...

// 之后(防御性)
await loadCompletedAssessmentAnswers(id);
const loaded = useAssessmentStore.getState().completedAssessmentAnswers;
if (Object.keys(loaded).length === 0) {
  throw new Error(
    `Failed to load answers for assessment ${id} - cannot proceed with retake`
  );
}

原则: 将每个异步调用视为可能失败,直到证明否则。

何时验证:

  • 加载数据后,后续操作依赖于这些数据
  • 状态更新后,必须在继续之前完成
  • 在不可逆操作之前(提交、删除)

模式模板:

await someAsyncOperation();
const result = getRelevantState();
if (!isValid(result)) {
  throw new Error(`[${functionName}] Post-condition failed: ${diagnosticContext}`);
}

模式:异步函数识别

问题: 不是所有异步函数看起来都是异步的。Zustand 动作、回调和返回承诺的函数可能没有明显的 async 关键字。

隐藏的异步模式:

// 明显的异步
async function fetchData() { ... }

// 较不明显 - 返回 Promise
function fetchData(): Promise<Data> { ... }

// 隐藏 - 实际上异步的 Zustand 动作
const useStore = create((set, get) => ({
  // 这看起来是同步的,但内部调用了异步
  enableRetake: (area: string) => {
    someAsyncSetup().then(() => {  // ← 隐藏的异步!
      set({ retakeAreas: [...get().retakeAreas, area] });
    });
  },
}));

// 正确的异步 Zustand 动作
const useStore = create((set, get) => ({
  enableRetake: async (area: string) => {
    await someAsyncSetup();
    set({ retakeAreas: [...get().retakeAreas, area] });
  },
}));

检测: 检查函数签名和实现:

# 查找返回 Promise 的函数
grep -rn "): Promise<" --include="*.ts" --include="*.tsx"

# 查找可能需要 await 的 .then() 链
grep -rn "\.then(" --include="*.ts" --include="*.tsx"

模式:顺序与并行异步

问题: 当它们可以并行时,顺序运行异步操作(慢),或者当它们必须是顺序时并行(竞态条件)。

// 顺序 - 当顺序重要时正确
await enableSkillAreaRetake(skillArea);
await clearSkillAreaAnswers(skillArea);
await loadRetakeQuestions(skillArea);

// 并行 - 当操作独立时正确
const [user, settings, history] = await Promise.all([
  fetchUser(id),
  fetchSettings(id),
  fetchHistory(id),
]);

// 错误 - 当顺序重要时并行
await Promise.all([
  enableSkillAreaRetake(skillArea),   // 这些有依赖关系!
  clearSkillAreaAnswers(skillArea),
]);

决策框架:

操作共享状态? 必须按顺序运行? 模式
Promise.all()
顺序 await
通常顺序以安全为上

模式:useEffect 中的异步

问题: useEffect 回调不能直接是异步的。常见的清理和竞态条件错误。

// 错误 - useEffect 不能是异步的
useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

// 正确 - 内部异步函数
useEffect(() => {
  async function load() {
    const data = await fetchData();
    setData(data);
  }
  load();
}, []);

// 更好 - 用于竞态条件的清理
useEffect(() => {
  let cancelled = false;
  
  async function load() {
    const data = await fetchData();
    if (!cancelled) {
      setData(data);
    }
  }
  load();
  
  return () => {
    cancelled = true;
  };
}, [dependency]);

ESLint 配置

应用 configs/eslint-async.json 中的配置,在 lint 时间捕获这些问题:

{
  "rules": {
    "@typescript-eslint/no-floating-promises": "error",
    "@typescript-eslint/require-await": "warn",
    "@typescript-eslint/await-thenable": "error",
    "@typescript-eslint/no-misused-promises": "error"
  }
}

所需: @typescript-eslint/eslint-plugin 和适当的 TypeScript 配置。


代码审查清单

审查异步代码时,请检查:

  • [ ] 每个异步函数调用都被 awaitreturn 或明确注释为火并忘记
  • [ ] 相互依赖的操作使用 await 顺序
  • [ ] 在关键异步操作后验证后置条件
  • [ ] 带有异步的 useEffect 使用内部函数模式
  • [ ] 在组件可能在异步期间卸载时考虑竞态条件
  • [ ] 异步失败时存在错误处理

快速调试

当出现异步定时问题时:

// 添加时间戳以追踪执行顺序
console.log(`[${Date.now()}] Starting enableRetake`);
await enableSkillAreaRetake(skillArea);
console.log(`[${Date.now()}] Finished enableRetake`);
console.log(`[${Date.now()}] Starting clearAnswers`);
await clearSkillAreaAnswers(skillArea);
console.log(`[${Date.now()}] Finished clearAnswers`);

查找:

  • 操作完成顺序不符合预期
  • 操作在前一个操作完成前开始
  • 异常快速的“完成”(可能没有等待)