Redux Thunk异常:getDeliveryBoxAsync被拒时handleBoxDeliveryAsync仍为fulfilled
问题:Redux Thunk中父Thunk未捕获子Thunk的Promise拒绝状态
问题场景
使用Redux Thunk时,handleBoxDeliveryAsync作为父Thunk会根据scanMode分发不同的子Thunk(getDeliveryBoxAsync/removeDeliveryBoxAsync)。但当子ThunkgetDeliveryBoxAsync被Promise.reject拒绝时,父ThunkhandleBoxDeliveryAsync却始终处于fulfilled状态,无法触发错误处理逻辑。
相关代码
Slice中的reducer处理:
extraReducers: (builder) => { builder .addCase(handleBoxDeliveryAsync.fulfilled, (state, action) => { if (state.scanMode === "remove") { return; } const payload = action?.payload?.payload as Data; const box = payload.data.resource; //If box hasn't COLLECTED status if (box.status !== "COLLECTED") { toast.warn("Le bac de collecte n'est pas disponible à la livraison"); return; } //If box hasn't been scanned yet if (!state.boxes.find((b) => b.external_id === box.external_id)) { state.boxes.push({ external_id: box.external_id, weight: 0 }); toast.success("Le bac a été ajouté"); return; } //If box has already been scanned toast.warn("Le bac a déjà été scanné"); }) ...
Thunk定义:
export const handleBoxDeliveryAsync = createAppAsyncThunk( "collectDelivery/handleBox", async (code: string, store) => { try { if (store.getState().deliveryCollect.scanMode === "add") { return store.dispatch(getDeliveryBoxAsync(code)); } if (store.getState().deliveryCollect.scanMode === "remove") { return store.dispatch(removeDeliveryBoxAsync(code)); } } catch (e) { return Promise.reject(e); // Explicitly reject the promise if an error occurs. } } ); export const getDeliveryBoxAsync = createAppAsyncThunk( "collectDelivery/getBox", async (code: string) => { try { return await getBox(code); } catch (e) { return Promise.reject(e); } } ); export const removeDeliveryBoxAsync = createAppAsyncThunk( "collectDelivery/removeBox", async (code: string, thunk) => { if ( thunk .getState() .deliveryCollect.boxes.map((b) => b.external_id) .includes(code) ) { return Promise.resolve(code); } return Promise.reject(code); } );
原因分析
store.dispatch()调用Thunk时,始终返回一个已resolved的Promise,无论内部子Thunk是fulfilled还是rejected。它返回的是Thunk执行后生成的action对象,而非子Thunk的Promise结果。因此父Thunk的try/catch无法捕获子Thunk的错误,导致父Thunk始终进入fulfilled状态。- 子Thunk中返回
Promise.reject(e)只会让子Thunk自身进入rejected状态,但父Thunk拿到的是dispatch返回的resolved结果,不会触发自身的catch块。
解决方案
1. 使用unwrap()提取子Thunk的真实Promise结果
修改handleBoxDeliveryAsync,通过unwrap()方法获取子Thunk的实际Promise状态,子Thunk拒绝时会直接抛出错误,被父Thunk捕获:
export const handleBoxDeliveryAsync = createAppAsyncThunk( "collectDelivery/handleBox", async (code: string, { getState, dispatch }) => { const scanMode = getState().deliveryCollect.scanMode; if (scanMode === "add") { // unwrap()会提取子Thunk的真实Promise结果,拒绝时抛出错误 const result = await dispatch(getDeliveryBoxAsync(code)).unwrap(); return result; } if (scanMode === "remove") { const result = await dispatch(removeDeliveryBoxAsync(code)).unwrap(); return result; } } );
2. 添加父Thunk的rejected状态处理
在slice的extraReducers中新增handleBoxDeliveryAsync.rejected的处理逻辑,响应错误场景:
extraReducers: (builder) => { builder .addCase(handleBoxDeliveryAsync.fulfilled, (state, action) => { // 原有的fulfilled逻辑 if (state.scanMode === "remove") { return; } const payload = action?.payload?.payload as Data; const box = payload.data.resource; if (box.status !== "COLLECTED") { toast.warn("Le bac de collecte n'est pas disponible à la livraison"); return; } if (!state.boxes.find((b) => b.external_id === box.external_id)) { state.boxes.push({ external_id: box.external_id, weight: 0 }); toast.success("Le bac a été ajouté"); return; } toast.warn("Le bac a déjà été scanné"); }) .addCase(handleBoxDeliveryAsync.rejected, (state, action) => { // 处理父Thunk被拒绝的情况 toast.error("扫描失败:" + (action.error.message || "未知错误")); }) ...
3. 简化子Thunk的错误处理
createAppAsyncThunk会自动捕获函数内抛出的错误并标记为rejected状态,无需手动返回Promise.reject:
export const getDeliveryBoxAsync = createAppAsyncThunk( "collectDelivery/getBox", async (code: string) => { // 直接返回请求结果,错误会被自动捕获 return await getBox(code); } ); export const removeDeliveryBoxAsync = createAppAsyncThunk( "collectDelivery/removeBox", async (code: string, { getState }) => { const hasBox = getState().deliveryCollect.boxes.some(b => b.external_id === code); if (hasBox) { return code; } // 直接抛出错误,createAppAsyncThunk会处理为rejected状态 throw new Error("该箱子未被扫描过"); } );
内容的提问来源于stack exchange,提问作者Ashitaaka
相关产品推荐
相关产品推荐

