Skip to content

Commit f586dad

Browse files
committed
Add rethrow flag to createAsyncThunk
1 parent a6e5e5f commit f586dad

File tree

3 files changed

+86
-9
lines changed

3 files changed

+86
-9
lines changed

etc/redux-toolkit.api.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export function createAction<P = void, T extends string = string>(type: T): Payl
103103
export function createAction<PA extends PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;
104104

105105
// @alpha (undocumented)
106-
export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(type: string, payloadCreator: (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => Promise<Returned> | Returned): ((arg: ThunkArg) => (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<Returned, string, {
106+
export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(type: string, payloadCreator: (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => Promise<Returned> | Returned, rethrow?: boolean): (((arg: ThunkArg, rethrow?: boolean) => (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<Returned, string, {
107107
arg: ThunkArg;
108108
requestId: string;
109109
}, never> | PayloadAction<undefined, string, {
@@ -126,7 +126,30 @@ export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig exten
126126
arg: ThunkArg;
127127
requestId: string;
128128
}>;
129-
};
129+
} & false) | (((arg: ThunkArg, rethrow?: boolean) => (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<Returned, string, {
130+
arg: ThunkArg;
131+
requestId: string;
132+
}, never> | PayloadAction<undefined, string, {
133+
arg: ThunkArg;
134+
requestId: string;
135+
aborted: boolean;
136+
}, any>> & {
137+
abort: (reason?: string | undefined) => void;
138+
}) & {
139+
pending: ActionCreatorWithPreparedPayload<[string, ThunkArg], undefined, string, never, {
140+
arg: ThunkArg;
141+
requestId: string;
142+
}>;
143+
rejected: ActionCreatorWithPreparedPayload<[Error, string, ThunkArg], undefined, string, any, {
144+
arg: ThunkArg;
145+
requestId: string;
146+
aborted: boolean;
147+
}>;
148+
fulfilled: ActionCreatorWithPreparedPayload<[Returned, string, ThunkArg], Returned, string, never, {
149+
arg: ThunkArg;
150+
requestId: string;
151+
}>;
152+
} & true);
130153

131154
// @alpha (undocumented)
132155
export function createEntityAdapter<T>(options?: {

src/createAsyncThunk.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,47 @@ describe('createAsyncThunk', () => {
106106
expect(errorAction.meta.requestId).toBe(generatedRequestId)
107107
expect(errorAction.meta.arg).toBe(args)
108108
})
109+
110+
it('accepts arguments, dispatches actions and then throws when rethrow is specified', async () => {
111+
const dispatch = jest.fn()
112+
113+
let passedArg: any
114+
115+
const result = 42
116+
const args = 123
117+
let generatedRequestId = ''
118+
119+
let rethrowErr
120+
121+
const error = new Error('Panic!')
122+
123+
const thunkActionCreator = createAsyncThunk(
124+
'app/fakeRequest',
125+
async (arg: number, { requestId }) => {
126+
passedArg = arg
127+
generatedRequestId = requestId
128+
throw error
129+
},
130+
true
131+
)
132+
133+
const thunkFunction = thunkActionCreator(args, true)
134+
135+
try {
136+
await thunkFunction(dispatch, () => {}, undefined)
137+
} catch (err) {
138+
rethrowErr = err
139+
}
140+
141+
expect(rethrowErr).toBeTruthy()
142+
expect(dispatch).toHaveBeenCalledTimes(2)
143+
144+
// Have to check the bits of the action separately since the error was processed
145+
const errorAction = dispatch.mock.calls[1][0]
146+
expect(errorAction.error).toEqual(miniSerializeError(error))
147+
expect(errorAction.meta.requestId).toBe(generatedRequestId)
148+
expect(errorAction.meta.arg).toBe(args)
149+
})
109150
})
110151

111152
describe('createAsyncThunk with abortController', () => {

src/createAsyncThunk.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type GetThunkAPI<ThunkApiConfig> = BaseThunkAPI<
8989
*
9090
* @param type
9191
* @param payloadCreator
92+
* @param rethrow
9293
*
9394
* @alpha
9495
*/
@@ -101,7 +102,8 @@ export function createAsyncThunk<
101102
payloadCreator: (
102103
arg: ThunkArg,
103104
thunkAPI: GetThunkAPI<ThunkApiConfig>
104-
) => Promise<Returned> | Returned
105+
) => Promise<Returned> | Returned,
106+
rethrow?: boolean
105107
) {
106108
const fulfilled = createAction(
107109
type + '/fulfilled',
@@ -139,13 +141,14 @@ export function createAsyncThunk<
139141
}
140142
)
141143

142-
function actionCreator(arg: ThunkArg) {
144+
function actionCreator(arg: ThunkArg, rethrow: boolean = false) {
143145
return (
144146
dispatch: GetDispatch<ThunkApiConfig>,
145147
getState: () => GetState<ThunkApiConfig>,
146148
extra: GetExtra<ThunkApiConfig>
147149
) => {
148150
const requestId = nanoid()
151+
let rethrowErr
149152

150153
const abortController = new AbortController()
151154
let abortReason: string | undefined
@@ -179,24 +182,34 @@ export function createAsyncThunk<
179182
])
180183
} catch (err) {
181184
finalAction = rejected(err, requestId, arg)
185+
rethrowErr = err
182186
}
183187
// We dispatch the result action _after_ the catch, to avoid having any errors
184188
// here get swallowed by the try/catch block,
185189
// per https://twitter.com/dan_abramov/status/770914221638942720
186190
// and https://redux-toolkit.js.org/tutorials/advanced-tutorial#async-error-handling-logic-in-thunks
187191

188192
dispatch(finalAction)
193+
194+
if (rethrow && rethrowErr) {
195+
throw rethrowErr
196+
}
197+
189198
return finalAction
190199
})()
191200
return Object.assign(promise, { abort })
192201
}
193202
}
194203

195-
return Object.assign(actionCreator, {
196-
pending,
197-
rejected,
198-
fulfilled
199-
})
204+
return Object.assign(
205+
actionCreator,
206+
{
207+
pending,
208+
rejected,
209+
fulfilled
210+
},
211+
rethrow
212+
)
200213
}
201214

202215
/**

0 commit comments

Comments
 (0)