为何JavaScript中await未抛错却终止执行?React场景排查
问题
在React自定义Hook中定义了异步函数onSearch,它在try..catch..finally块内await基于react-query封装的searchMutation执行结果。当短时间内多次调用onSearch时,onSuccess返回undefined,且await后的代码静默终止,无任何错误抛出。按道理Promise返回undefined不应该阻止后续代码执行,想知道这是怎么发生的,以及有哪些调试思路。
自定义Hook代码
const useCustomHook = () => { const searchMutation = useSearchMutation(); const onSearch = async (searchParams: ISearchParams) => { try { // 一些前置代码 const response = await searchMutation({params}); // 处理结果的代码(静默终止在这里) } catch (error) { // 错误处理代码 } finally { // 最终执行的代码 } } return { onSearch }; }
SearchMutation封装代码
const mutatePromise = async (variables: Variables, options?: IMutationOptions<Data, Error, Variables>) => { return new Promise<Data>((resolve, reject) => { result.mutate(variables, { ...options, onSuccess: data => { resolve(data); }, onError: error => { reject(error); }, }); }); };
mutation函数实现
const result = _useMutation<Data, Error, Variables>({ mutationKey: url, mutationFn: async variables => { const method = options?.method || "POST"; const response = await fetch(url, { method, headers: { Accept: options?.accept || "application/json", "Content-Type": "application/json", }, body: variables ? JSON.stringify(variables) : undefined, }); if (options?.accept === "text/csv") { const filename = getFilenameFromHeaders(response.headers); const blob = await response.blob(); downloadFile(blob, filename); return; } else { const contentLength = Number(response.headers.get("Content-Length")); if (contentLength > 0) { const responseValue = await response.json(); if ((responseValue?.hasOwnProperty("isSuccess") && !responseValue.isSuccess) || !response.ok) { throw responseValue; } return responseValue; } } if (!response.ok) { throw new Error(); } }, ...options, });
原因分析
- React Query的请求取消机制:短时间内多次触发mutation时,React Query默认会取消前一个未完成的请求。但你封装的
mutatePromise没有处理请求取消的场景,导致对应的Promise既不resolve也不reject,await会一直处于挂起状态,后续代码自然不会执行。 - mutationFn的返回逻辑漏洞:在非CSV请求分支中,如果
contentLength <= 0且response.ok为true,mutationFn没有返回任何值,最终会resolve(undefined)。但这本身不会导致代码终止,真正的问题还是前面的请求未被正确处理取消。 - 手动Promise封装的缺陷:你自己封装的
mutatePromise只处理了onSuccess和onError,但React Query的mutate还包含取消回调,未处理该场景会让Promise一直处于pending状态,await永远不会结束。
调试思路
- 添加取消错误处理:在
mutate的配置中,给onError添加对CanceledError的判断,捕获到取消错误后主动reject,这样await会进入catch块,不会静默挂起:onError: (error) => { // 导入react-query的CanceledError if (error instanceof CanceledError) { reject(error); } else { reject(error); } } - 监控Promise状态:在
onSearch中给searchMutation的Promise单独加finally回调,打印日志确认Promise是否完成:
如果日志没输出,说明Promise处于pending状态,大概率是请求被取消了。const response = await searchMutation({params}).finally(() => console.log("Promise已完成")); - 查看mutation状态:在自定义Hook中打印
searchMutation的内置状态,多次调用时观察是否有取消发生:const searchMutation = useSearchMutation(); console.log("mutation状态:", { isLoading: searchMutation.isLoading, isCanceled: searchMutation.isCanceled }); - 补全mutationFn返回逻辑:在非CSV分支中,当
contentLength <= 0时,明确返回一个默认值(比如{}),避免resolve(undefined)的模糊情况,同时确保所有代码路径都有返回或抛出错误。 - 替换为React Query原生的mutateAsync:React Query已经提供了
mutateAsync方法,无需手动封装Promise,它会自动处理成功、失败和取消场景,直接替换你封装的mutatePromise即可:const useSearchMutation = () => { const result = _useMutation(/* 原有配置 */); return result.mutateAsync; }
内容的提问来源于stack exchange,提问作者Arkadiusz Kałkus
相关产品推荐
相关产品推荐

