React自定义Hook异步调用问题:await链式调用不生效
解决React自定义Hook异步请求顺序执行问题
首先,咱们先拆解下你遇到的核心问题:你的doFetch函数并没有返回一个等待请求完成的Promise,而且自定义Hook里用了共享状态存储请求参数,导致两次调用doFetch时会互相覆盖状态,最终await根本没起到等待请求完成的作用。
问题根源分析
doFetch未返回Promise:你当前的doFetch只是更新组件状态(setUrl、setMethod等),这些状态更新都是同步操作,所以await doFetch()会立刻执行完成,不会等待实际的fetch请求结束。- 共享状态被覆盖:两次连续调用
doFetch会快速覆盖url、obj等共享状态,导致第一个请求的useEffect还没执行完,就被第二个请求的状态更新打断,甚至因为didCancel标记,第一个请求的成功回调会被忽略。 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); } }
关键修改点说明
doFetch改为async函数:直接返回Promise,让await能正确等待请求完成。- 移除共享状态:不再用
url、method等useState变量存储请求参数,每个doFetch调用独立处理自己的请求参数,避免互相覆盖。 - 添加AbortController:更可靠地处理组件卸载时的请求取消,不仅忽略结果,还能真正中断网络请求,节省资源。
- 完善错误处理:手动处理HTTP状态码错误,让错误逻辑更完整。
这样修改后,你的两次请求就会严格按照await的顺序执行:先等待删除请求完成,再发起列表查询请求,完全符合你的预期。
内容的提问来源于stack exchange,提问作者Fabio Russo
相关产品推荐
相关产品推荐

