React函数组件如何捕获Redux Thunk异步方法抛出的错误
React函数组件捕获Redux Thunk异步错误的解决方案
问题根因
当前组件侧.catch()无法触发的核心原因是:thunk内部的.catch()回调已经捕获了接口请求抛出的Promise错误,最终thunk返回的是fulfilled(已完成)状态的Promise,错误被内部逻辑完全消化,外层调用方自然无法进入reject回调。
之前尝试在thunk错误分支直接抛错的思路是对的,单测失败本质是现有单测本身存在异步逻辑未等待、断言未执行的隐性问题,不是方案逻辑错误。
推荐方案:修正thunk错误传递逻辑,补全单测异步等待
这种方案符合异步逻辑的设计惯例,调用方可以通过原生Promise的then/catch区分成功失败状态,长期可维护性更好。
- 修改thunk逻辑,派发完错误action后将错误继续抛出,向外传递失败状态:
export const updateCostCenter = (data: Record<string, unknown>) => async (dispatch: Dispatch<IWorkforceState>) => { dispatch(requestUpdateCostCenter()); return api('put', `${costCenterUrl}/${data.rowId}`, data) .then(response => { return dispatch(receiveUpdateCostCenter(response.data)); }) .catch(err => { dispatch(errorUpdateCostCenter(err.response?.data?.description)); // 向外抛出错误,让调用方感知请求失败 throw err; }); };
- 修正单元测试:现有单测没有将异步Promise返回给测试框架,框架不会等待异步逻辑执行完成就会结束用例,导致错误场景下的断言根本没有执行(属于无效单测)。给每个用例加上return返回Promise即可正常通过:
it('update cost center success', function() { mockAdd.mockImplementation( () => Promise.resolve({ data: costCenter } as any) ); const expectedActions = [ { type: WorkforceActions.REQUEST_UPDATE_COST_CENTER }, { type: WorkforceActions.RECEIVE_UPDATE_COST_CENTER, costCenter }, ]; // 返回Promise,让测试框架等待异步逻辑执行完成 return store.dispatch(updateCostCenter({ data: costCenter }) as any).then(() => { expect(store.getActions()).toEqual(expectedActions); expect(api).toHaveBeenCalled(); }); }); it('update cost center error', function() { mockAdd.mockImplementation( () => Promise.reject({ response: { data: { description: 'dummy-message' } }, } as any) ); const expectedActions = [ { type: WorkforceActions.REQUEST_UPDATE_COST_CENTER }, { type: WorkforceActions.ERROR_UPDATE_COST_CENTER, message: 'dummy-message' }, ]; // 错误场景thunk会返回rejected状态的Promise,刚好进入catch执行断言 return store.dispatch(updateCostCenter({ data: costCenter }) as any).catch(() => { expect(store.getActions()).toEqual(expectedActions); expect(api).toHaveBeenCalled(); }); });
改完之后组件侧原有catch逻辑可以正常触发,注意要在错误分支补充setSubmitting(false)逻辑,避免表单一直卡在提交状态。
兼容方案:不修改thunk状态,通过返回值判断执行结果
如果暂时不想调整thunk和现有单测,可以让thunk在不同执行结果下返回状态标识,组件侧在then回调里判断结果即可,不需要依赖Promise的catch捕获。
- 调整thunk返回值:
export const updateCostCenter = (data: Record<string, unknown>) => async (dispatch: Dispatch<IWorkforceState>) => { dispatch(requestUpdateCostCenter()); return api('put', `${costCenterUrl}/${data.rowId}`, data) .then(response => { dispatch(receiveUpdateCostCenter(response.data)); return { success: true }; }) .catch(err => { dispatch(errorUpdateCostCenter(err.response?.data?.description)); return { success: false }; }); };
- 组件侧调整调用逻辑:
props.updateCostCenter(valueToSubmit).then((result) => { setSubmitting(false); if (!result.success) { props.showToastNotification('error', 'Error', props.translate('cost_center_update_error')); return; } props.showToastNotification('success', 'Successful', props.translate('cost_center_successfully_updated')); AmplitudeService.logEvent(props.translate('edit_cost_center')); props.hideDialog(); resetForm(); if (props.loadData) { props.loadData(); } });
这种方案下thunk永远返回fulfilled状态的Promise,原有单测不需要改动即可运行,但要注意原有错误场景的单测因为没有等待异步逻辑,本身属于无效用例,后续建议补全。
内容的提问来源于stack exchange,提问作者stratdev
相关产品推荐
相关产品推荐

