Skip to content

Commit 5f7afe9

Browse files
committed
createAsyncThunk return fulfilled/rejected action instead of re-trowing errors
1 parent fbba32d commit 5f7afe9

File tree

3 files changed

+46
-25
lines changed

3 files changed

+46
-25
lines changed

etc/redux-toolkit.api.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,13 @@ 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): {
106-
(args: ActionParams): (dispatch: TA["dispatch"], getState: TA["getState"], extra: TA["extra"]) => Promise<any>;
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): ((args: ActionParams) => (dispatch: TA["dispatch"], getState: TA["getState"], extra: TA["extra"]) => Promise<import("./createAction").PayloadAction<Returned, string, {
106+
args: ActionParams;
107+
requestId: string;
108+
}, never> | import("./createAction").PayloadAction<undefined, string, {
109+
args: ActionParams;
110+
requestId: string;
111+
}, Error>>) & {
107112
pending: import("./createAction").ActionCreatorWithPreparedPayload<[string, ActionParams], undefined, string, never, {
108113
args: ActionParams;
109114
requestId: string;

src/createAsyncThunk.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -101,34 +101,33 @@ export function createAsyncThunk<
101101
) => {
102102
const requestId = nanoid()
103103

104-
let result: Returned
104+
let finalAction: ReturnType<typeof fulfilled | typeof rejected>
105105
try {
106106
dispatch(pending(requestId, args))
107107

108-
result = (await payloadCreator(args, {
109-
dispatch,
110-
getState,
111-
extra,
112-
requestId
113-
} as TA)) as Returned
108+
finalAction = fulfilled(
109+
await payloadCreator(args, {
110+
dispatch,
111+
getState,
112+
extra,
113+
requestId
114+
} as TA),
115+
requestId,
116+
args
117+
)
114118
} catch (err) {
115119
const serializedError = miniSerializeError(err)
116-
dispatch(rejected(serializedError, requestId, args))
117-
// Rethrow this so the user can handle if desired
118-
throw err
120+
finalAction = rejected(serializedError, requestId, args)
119121
}
120122

121123
// We dispatch "success" _after_ the catch, to avoid having any errors
122124
// here get swallowed by the try/catch block,
123125
// per https://twitter.com/dan_abramov/status/770914221638942720
124126
// and https://redux-toolkit.js.org/tutorials/advanced-tutorial#async-error-handling-logic-in-thunks
125-
return dispatch(fulfilled(result!, requestId, args))
127+
dispatch(finalAction)
128+
return finalAction
126129
}
127130
}
128131

129-
actionCreator.pending = pending
130-
actionCreator.rejected = rejected
131-
actionCreator.fulfilled = fulfilled
132-
133-
return actionCreator
132+
return Object.assign(actionCreator, { pending, rejected, fulfilled })
134133
}
Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createAsyncThunk, Dispatch, createReducer } from 'src'
1+
import { createAsyncThunk, Dispatch, createReducer, AnyAction } from 'src'
22
import { ThunkDispatch } from 'redux-thunk'
33

44
function expectType<T>(t: T) {
@@ -7,20 +7,37 @@ function expectType<T>(t: T) {
77
function fn() {}
88

99
// basic usage
10-
{
11-
const dispatch = fn as ThunkDispatch<any, any, any>
10+
;(async function() {
11+
const dispatch = fn as ThunkDispatch<{}, any, AnyAction>
1212

1313
const async = createAsyncThunk('test', (id: number) =>
1414
Promise.resolve(id * 2)
1515
)
16-
dispatch(async(3))
1716

1817
const reducer = createReducer({}, builder =>
1918
builder
20-
.addCase(async.pending, (_, action) => {})
19+
.addCase(async.pending, (_, action) => {
20+
expectType<ReturnType<typeof async['pending']>>(action)
21+
})
2122
.addCase(async.fulfilled, (_, action) => {
23+
expectType<ReturnType<typeof async['fulfilled']>>(action)
2224
expectType<number>(action.payload)
2325
})
24-
.addCase(async.rejected, (_, action) => {})
26+
.addCase(async.rejected, (_, action) => {
27+
expectType<ReturnType<typeof async['rejected']>>(action)
28+
expectType<Error>(action.error)
29+
})
2530
)
26-
}
31+
32+
const result = await dispatch(async(3))
33+
34+
if (async.fulfilled.match(result)) {
35+
expectType<ReturnType<typeof async['fulfilled']>>(result)
36+
// typings:expect-error
37+
expectType<ReturnType<typeof async['rejected']>>(result)
38+
} else {
39+
expectType<ReturnType<typeof async['rejected']>>(result)
40+
// typings:expect-error
41+
expectType<ReturnType<typeof async['fulfilled']>>(result)
42+
}
43+
})()

0 commit comments

Comments
 (0)