Skip to content

Commit 4d1daaa

Browse files
committed
Add optional thunk configuration object
- User can specify a simple string for type or a config object. - Config can choose to rethrow an error. - Other possible use cases; - Prime result function to transform result before return. - Prime error function to transform error before return.
1 parent 9d57d62 commit 4d1daaa

File tree

3 files changed

+69
-4
lines changed

3 files changed

+69
-4
lines changed

etc/redux-toolkit.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export function createAction<P = void, T extends string = string>(type: T): Payl
102102
export function createAction<PA extends PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;
103103

104104
// @alpha (undocumented)
105-
export function createAsyncThunk<ActionType extends string, Returned, ActionParams = void, TA extends AsyncThunksArgs<any, any, any> = AsyncThunksArgs<unknown, unknown, Dispatch>>(type: ActionType, payloadCreator: (args: ActionParams, thunkArgs: TA) => Promise<Returned> | Returned): {
105+
export function createAsyncThunk<ActionType extends string | ThunkActionCreatorConfig, Returned, ActionParams = void, TA extends AsyncThunksArgs<any, any, any> = AsyncThunksArgs<unknown, unknown, Dispatch>>(config: ActionType, payloadCreator: (args: ActionParams, thunkArgs: TA) => Promise<Returned> | Returned): {
106106
(args: ActionParams): (dispatch: TA["dispatch"], getState: TA["getState"], extra: TA["extra"]) => Promise<any>;
107107
pending: import("./createAction").ActionCreatorWithPreparedPayload<[string, ActionParams], undefined, string, never, {
108108
args: ActionParams;

src/createAsyncThunk.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,46 @@ describe('createAsyncThunk', () => {
1111
expect(thunkActionCreator.rejected.type).toBe('testType/rejected')
1212
})
1313

14+
it('should accept a config object', () => {
15+
const thunkActionCreator = createAsyncThunk(
16+
{ type: 'testType' },
17+
async () => 42
18+
)
19+
20+
expect(thunkActionCreator.fulfilled.type).toBe('testType/fulfilled')
21+
expect(thunkActionCreator.pending.type).toBe('testType/pending')
22+
expect(thunkActionCreator.finished.type).toBe('testType/finished')
23+
expect(thunkActionCreator.rejected.type).toBe('testType/rejected')
24+
})
25+
26+
it('should rethrow error', async () => {
27+
const dispatch = jest.fn()
28+
29+
const args = 123
30+
let generatedRequestId = ''
31+
32+
const error = new Error('Panic!')
33+
34+
const thunkActionCreator = createAsyncThunk(
35+
{ type: 'testType', rethrow: true },
36+
async (args: number, { requestId }) => {
37+
generatedRequestId = requestId
38+
throw error
39+
}
40+
)
41+
42+
const thunkFunction = thunkActionCreator(args)
43+
44+
await expect(thunkFunction(dispatch, undefined, undefined)).rejects.toThrow(
45+
error
46+
)
47+
48+
expect(dispatch).toHaveBeenNthCalledWith(
49+
2,
50+
thunkActionCreator.rejected(error, generatedRequestId, args)
51+
)
52+
})
53+
1454
it('works without passing arguments to the payload creator', async () => {
1555
const thunkActionCreator = createAsyncThunk('testType', async () => 42)
1656

src/createAsyncThunk.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,37 @@ type AsyncThunksArgs<S, E, D extends Dispatch = Dispatch> = {
99
requestId: string
1010
}
1111

12+
interface ThunkActionCreatorConfig {
13+
type: string
14+
rethrow?: boolean
15+
}
16+
17+
type ActionType = string | ThunkActionCreatorConfig
18+
19+
function isConfig(type: ActionType): type is ThunkActionCreatorConfig {
20+
return (type as ThunkActionCreatorConfig).type !== undefined
21+
}
22+
23+
function buildOptions(
24+
config: string | ThunkActionCreatorConfig
25+
): ThunkActionCreatorConfig {
26+
return isConfig(config)
27+
? config
28+
: {
29+
type: config,
30+
rethrow: false
31+
}
32+
}
33+
1234
/**
1335
*
14-
* @param type
36+
* @param config
1537
* @param payloadCreator
1638
*
1739
* @alpha
1840
*/
1941
export function createAsyncThunk<
20-
ActionType extends string,
42+
ActionType extends string | ThunkActionCreatorConfig,
2143
Returned,
2244
ActionParams = void,
2345
TA extends AsyncThunksArgs<any, any, any> = AsyncThunksArgs<
@@ -26,12 +48,14 @@ export function createAsyncThunk<
2648
Dispatch
2749
>
2850
>(
29-
type: ActionType,
51+
config: ActionType,
3052
payloadCreator: (
3153
args: ActionParams,
3254
thunkArgs: TA
3355
) => Promise<Returned> | Returned
3456
) {
57+
const { type, rethrow } = buildOptions(config)
58+
3559
const fulfilled = createAction(
3660
type + '/fulfilled',
3761
(result: Returned, requestId: string, args: ActionParams) => {
@@ -96,6 +120,7 @@ export function createAsyncThunk<
96120
} catch (err) {
97121
// TODO Errors aren't serializable
98122
dispatch(rejected(err, requestId, args))
123+
if (rethrow) throw err
99124
} finally {
100125
// TODO IS there really a benefit from a "finished" action?
101126
dispatch(finished(requestId, args))

0 commit comments

Comments
 (0)