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

React自定义Hook异步调用问题:await链式调用不生效

解决React自定义Hook异步请求顺序执行问题

首先,咱们先拆解下你遇到的核心问题:你的doFetch函数并没有返回一个等待请求完成的Promise,而且自定义Hook里用了共享状态存储请求参数,导致两次调用doFetch时会互相覆盖状态,最终await根本没起到等待请求完成的作用。

问题根源分析

  1. doFetch未返回Promise:你当前的doFetch只是更新组件状态(setUrl、setMethod等),这些状态更新都是同步操作,所以await doFetch()会立刻执行完成,不会等待实际的fetch请求结束。
  2. 共享状态被覆盖:两次连续调用doFetch会快速覆盖url、obj等共享状态,导致第一个请求的useEffect还没执行完,就被第二个请求的状态更新打断,甚至因为didCancel标记,第一个请求的成功回调会被忽略。
  3. useEffect依赖不全:你的useEffect依赖数组只包含method和body,但实际触发fetch需要url和obj的变化,这会导致请求触发时机不可控。

修复方案:重构自定义Hook

我们需要让doFetch直接返回Promise,并且每个请求都独立处理,不再依赖共享状态。这样await就能正确等待每个请求完成,保证顺序执行。

修改后的useFetch代码

function useFetch() {
  const [state, dispatch] = useStateValue();

  const doFetch = async (url, method, obj, body = "") => {
    // 触发请求初始化的action
    dispatch({ type: `FETCH_${obj}_INIT` });
    let didCancel = false;
    const abortController = new AbortController();

    try {
      const response = await fetch(
        `https://myapiAddress${url}`,
        {
          method: method,
          // 处理请求体:有body时序列化并添加Content-Type头
          body: body ? JSON.stringify(body) : undefined,
          headers: new Headers({
            Authorization: myFunctionForAuthorization,
            ...(body && { "Content-Type": "application/json" })
          }),
          signal: abortController.signal // 用于取消请求
        }
      );

      // 处理HTTP错误(fetch仅在网络失败时抛出错误,HTTP状态码错误需要手动处理)
      if (!response.ok) {
        throw new Error(`请求失败,状态码:${response.status}`);
      }

      const result = await response.json();

      // 如果组件未卸载/请求未取消,触发成功action
      if (!didCancel) {
        dispatch({ type: `FETCH_${obj}_SUCCESS`, payload: result });
      }

      return result;
    } catch (error) {
      // 忽略请求取消的错误
      if (error.name === "AbortError") {
        console.log("请求已取消");
        return;
      }

      // 如果组件未卸载/请求未取消,触发错误action
      if (!didCancel) {
        dispatch({ type: `FETCH_${obj}_ERROR`, payload: { error } });
      }

      // 抛出错误让调用方可以捕获处理
      throw error;
    } finally {
      // 返回清理函数,组件卸载时取消请求并标记didCancel
      return () => {
        didCancel = true;
        abortController.abort();
      };
    }
  };

  return [doFetch];
}

export default useFetch;

调用方代码调整(只需补充缺失的obj参数)

你的按钮点击逻辑几乎不用改,只需要注意第一个doFetch调用要传入正确的obj参数(对应你redux里的action类型):

async function deleteAssignedOpp(id) {
  try {
    // 补充第一个请求的obj参数,比如"ASSIGNED_OPP",对应FETCH_ASSIGNED_OPP_INIT等action
    await doFetch(`/opportuniy/assigned/${id}`, "DELETE", "ASSIGNED_OPP");
    await doFetch("/opportuniy/assigned/list", "GET", "IN_PROGRESS");
  } catch (error) {
    // 这里可以添加错误提示,比如弹窗告知用户删除失败
    console.error("操作出错:", error);
  }
}

关键修改点说明

  1. doFetch改为async函数:直接返回Promise,让await能正确等待请求完成。
  2. 移除共享状态:不再用url、method等useState变量存储请求参数,每个doFetch调用独立处理自己的请求参数,避免互相覆盖。
  3. 添加AbortController:更可靠地处理组件卸载时的请求取消,不仅忽略结果,还能真正中断网络请求,节省资源。
  4. 完善错误处理:手动处理HTTP状态码错误,让错误逻辑更完整。

这样修改后,你的两次请求就会严格按照await的顺序执行:先等待删除请求完成,再发起列表查询请求,完全符合你的预期。

内容的提问来源于stack exchange,提问作者Fabio Russo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:03:19