You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tanstack Query:带回滚的多链式POST请求实现问题

Tanstack Query 链式POST请求与回滚实现方案

核心实现思路

  • 给每个业务POST请求配套实现回滚接口(比如创建资源后回滚就删除该资源,提交数据后回滚就重置对应数据),并封装成Tanstack Query的mutation。
  • 用一个总mutation串联所有请求流程,把链式调用、失败回滚逻辑都放在这个总mutation的执行函数里统一处理,避免多个独立mutation的状态混乱。
  • 用分层try-catch包裹每一步请求:成功后保存关键数据(比如创建的资源ID,用于回滚),失败时立即触发已成功步骤的回滚操作,同时做好回滚失败的日志记录。

代码示例

1. Service层封装(service.tsx)

import { useMutation } from '@tanstack/react-query';

// 请求1:创建资源A
const createResourceA = async (data: ResourceAData) => {
  const res = await fetch('/api/resource-a', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) throw new Error('创建资源A失败');
  return res.json();
};

// 回滚请求1:删除资源A
const rollbackResourceA = async (resourceAId: string) => {
  const res = await fetch(`/api/resource-a/${resourceAId}`, {
    method: 'DELETE',
  });
  if (!res.ok) throw new Error('回滚资源A失败');
};

// 请求2:创建资源B
const createResourceB = async (data: ResourceBData) => {
  const res = await fetch('/api/resource-b', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) throw new Error('创建资源B失败');
  return res.json();
};

// 回滚请求2:删除资源B
const rollbackResourceB = async (resourceBId: string) => {
  const res = await fetch(`/api/resource-b/${resourceBId}`, {
    method: 'DELETE',
  });
  if (!res.ok) throw new Error('回滚资源B失败');
};

// 请求3:提交最终数据
const submitFinalData = async (data: FinalData) => {
  const res = await fetch('/api/final-submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) throw new Error('提交最终数据失败');
  return res.json();
};

// 导出单个请求的mutation(可选,也可直接在总mutation里调用函数)
export const useCreateResourceAMutation = () => useMutation({
  mutationFn: createResourceA,
});

export const useRollbackResourceAMutation = () => useMutation({
  mutationFn: rollbackResourceA,
});

// 同理导出其他mutation...

2. 总mutation与组件使用

import { useMutation } from '@tanstack/react-query';
import { createResourceA, rollbackResourceA, createResourceB, rollbackResourceB, submitFinalData } from './service';

const useChainPostMutation = () => {
  return useMutation({
    mutationFn: async (payload: { 
      resourceAData: ResourceAData; 
      resourceBData: ResourceBData; 
      finalData: FinalData 
    }) => {
      const { resourceAData, resourceBData, finalData } = payload;
      let resourceAId: string | undefined;
      let resourceBId: string | undefined;

      try {
        // 步骤1:发起第一个请求
        const resourceA = await createResourceA(resourceAData);
        resourceAId = resourceA.id;

        try {
          // 步骤2:请求1成功,发起第二个请求
          const resourceB = await createResourceB(resourceBData);
          resourceBId = resourceB.id;

          try {
            // 步骤3:请求2成功,发起第三个请求
            await submitFinalData(finalData);
            return { success: true, message: '所有请求执行成功' };
          } catch (submitError) {
            // 步骤3失败,回滚请求1和2
            console.error('提交失败,开始回滚:', submitError);
            try {
              if (resourceBId) await rollbackResourceB(resourceBId);
              if (resourceAId) await rollbackResourceA(resourceAId);
            } catch (rollbackError) {
              console.error('回滚资源A/B失败:', rollbackError);
              // 这里可以加告警逻辑,比如通知后端或记录日志
            }
            throw new Error('提交最终数据失败,已尝试回滚资源A和B');
          }
        } catch (resourceBError) {
          // 步骤2失败,回滚请求1
          console.error('创建资源B失败,回滚资源A:', resourceBError);
          try {
            if (resourceAId) await rollbackResourceA(resourceAId);
          } catch (rollbackError) {
            console.error('回滚资源A失败:', rollbackError);
          }
          throw new Error('创建资源B失败,已尝试回滚资源A');
        }
      } catch (resourceAError) {
        // 步骤1失败,直接终止
        throw new Error('创建资源A失败,操作终止');
      }
    },
    onSuccess: (data) => {
      console.log(data.message);
      // 这里加成功后的UI提示,比如弹窗
    },
    onError: (error) => {
      console.error(error.message);
      // 这里加错误提示,比如Toast
    },
  });
};

// 组件中使用
const MyComponent = () => {
  const chainMutation = useChainPostMutation();

  const handleSubmit = async () => {
    try {
      await chainMutation.mutateAsync({
        resourceAData: { /* 资源A的请求数据 */ },
        resourceBData: { /* 资源B的请求数据 */ },
        finalData: { /* 最终提交的数据 */ },
      });
    } catch (err) {
      // 处理错误,比如显示错误信息
    }
  };

  return (
    <button onClick={handleSubmit} disabled={chainMutation.isLoading}>
      {chainMutation.isLoading ? '处理中...' : '执行链式请求'}
    </button>
  );
};

export default MyComponent;

关键注意事项

  • 回滚接口幂等性:回滚操作要保证多次调用不会出错(比如重复删除同一个资源不报错),避免回滚失败导致数据不一致。
  • 错误日志记录:回滚失败时一定要记详细日志,方便后续排查数据问题。
  • 状态统一管理:总mutation的isLoading状态可以统一控制整个流程的加载状态,不用单独处理多个mutation的加载状态。
  • 优先后端事务:如果后端支持数据库事务,优先让后端把三个请求放在一个事务里处理,失败自动回滚,前端只发一个请求即可,这样更可靠。

内容的提问来源于stack exchange,提问作者Jeff Barnes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 07:13:16