Redux Toolkit中使用Promise.all遇请求异常仍执行问题排查
问题分析
- 请求并行触发无法中断:
handleUpdate中同时dispatch两个thunk,会立刻发起两个API请求,即使其中一个快速返回错误,另一个请求已经发出,无法撤回。 - Thunk错误处理未正确抛出异常:
updateUser和updateCurrentUserAvatar的catch块仅打印错误但未重新抛出,导致thunk的Promise始终处于resolved状态,Promise.all无法捕获错误,后续错误处理逻辑失效。 - 冗余手动触发fulfilled action:createAsyncThunk在请求成功后会自动触发对应的fulfilled case,无需手动dispatch
updateCurrentUserAvatar.fulfilled和updateUser.fulfilled。
解决方案
1. 修复Thunk的错误抛出逻辑
修改两个createAsyncThunk的catch块,重新抛出错误,确保API失败时thunk的Promise进入rejected状态:
export const updateUser = createAsyncThunk('superadmin/updateProfile', async (payload) => { try { const response = await updateProfile({ email: payload.email, fullname: payload.fullname, dob: payload.dob, gender: payload.gender, phoneNumber: payload.phoneNumber, }); if (response.statusCode === 200) { return response; } else { throw new Error('Response status is not 200'); } } catch (error) { console.log(error); // 重新抛出错误,让thunk的Promise变为rejected throw error; } }); export const updateCurrentUserAvatar = createAsyncThunk('superadmin/updateAvatar', async (data) => { try { const formData = new FormData(); formData.append('file', data); const response = await axios.post('User/avatar', formData); if (response.statusCode === 200) { return response; } else { throw new Error('Response status is not 200'); } } catch (error) { console.log(error); // 重新抛出错误 throw error; } });
2. 控制请求触发时机(二选一)
选项A:顺序执行请求(避免无效请求)
如果希望只有第一个请求成功时才发起第二个,减少无效API调用:
const handleUpdate = async () => { try { // 先更新用户资料,成功后再上传头像 const userUpdateResult = await dispatch(updateUser(formData)).unwrap(); const avatarUpdateResult = await dispatch(updateCurrentUserAvatar(updateImage)).unwrap(); // 两个请求都成功,执行后续逻辑(如关闭弹窗) setOpen(false); } catch (error) { console.error('更新失败:', error); // 这里可以添加用户提示,比如alert('更新失败,请检查输入') } };
注:使用
.unwrap()可以获取thunk的实际返回值或直接抛出错误,因为dispatch返回的是带有unwrap方法的Promise。
选项B:并行请求+取消令牌(支持并行可中断)
如果需要并行请求,但希望在其中一个失败时取消另一个未完成的请求,可使用axios的取消令牌:
首先修改thunk,添加取消令牌支持:
export const updateUser = createAsyncThunk('superadmin/updateProfile', async (payload, { signal }) => { try { const source = axios.CancelToken.source(); // 监听abort信号,触发时取消请求 signal.addEventListener('abort', () => { source.cancel('因其他请求失败,终止当前请求'); }); const response = await updateProfile({ email: payload.email, fullname: payload.fullname, dob: payload.dob, gender: payload.gender, phoneNumber: payload.phoneNumber, cancelToken: source.token }); if (response.statusCode === 200) { return response; } else { throw new Error('Response status is not 200'); } } catch (error) { // 忽略取消请求的错误 if (!axios.isCancel(error)) { console.log(error); throw error; } } }); export const updateCurrentUserAvatar = createAsyncThunk('superadmin/updateAvatar', async (data, { signal }) => { try { const source = axios.CancelToken.source(); signal.addEventListener('abort', () => { source.cancel('因其他请求失败,终止当前请求'); }); const formData = new FormData(); formData.append('file', data); const response = await axios.post('User/avatar', formData, { cancelToken: source.token }); if (response.statusCode === 200) { return response; } else { throw new Error('Response status is not 200'); } } catch (error) { if (!axios.isCancel(error)) { console.log(error); throw error; } } });
然后在组件中使用Promise.all并处理取消逻辑:
const handleUpdate = async () => { const promise1 = dispatch(updateCurrentUserAvatar(updateImage)); const promise2 = dispatch(updateUser(formData)); try { const [avatarResult, userResult] = await Promise.all([ promise1.unwrap(), promise2.unwrap() ]); // 两个请求都成功 setOpen(false); } catch (error) { console.error('更新失败:', error); // 取消另一个未完成的请求 promise1.abort(); promise2.abort(); } };
3. 移除冗余代码
删除handleUpdate中手动触发fulfilled action的代码:
// 移除以下两行 dispatch(updateCurrentUserAvatar.fulfilled(result1.payload)); dispatch(updateUser.fulfilled(result2.payload));
内容的提问来源于stack exchange,提问作者Quang Phú Nguyễn Hoàng
相关产品推荐
相关产品推荐

