Skip to content

Commit

Permalink
createAsyncThunk return fulfilled/rejected action instead of re-trowi…
Browse files Browse the repository at this point in the history
…ng errors
  • Loading branch information
phryneas committed Feb 15, 2020
1 parent fbba32d commit 5f7afe9
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 25 deletions.
9 changes: 7 additions & 2 deletions etc/redux-toolkit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,13 @@ export function createAction<P = void, T extends string = string>(type: T): Payl
export function createAction<PA extends PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;

// @alpha (undocumented)
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<any>;
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, {
args: ActionParams;
requestId: string;
}, never> | import("./createAction").PayloadAction<undefined, string, {
args: ActionParams;
requestId: string;
}, Error>>) & {
pending: import("./createAction").ActionCreatorWithPreparedPayload<[string, ActionParams], undefined, string, never, {
args: ActionParams;
requestId: string;
Expand Down
31 changes: 15 additions & 16 deletions src/createAsyncThunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,34 +101,33 @@ export function createAsyncThunk<
) => {
const requestId = nanoid()

let result: Returned
let finalAction: ReturnType<typeof fulfilled | typeof rejected>
try {
dispatch(pending(requestId, args))

result = (await payloadCreator(args, {
dispatch,
getState,
extra,
requestId
} as TA)) as Returned
finalAction = fulfilled(
await payloadCreator(args, {
dispatch,
getState,
extra,
requestId
} as TA),
requestId,
args
)
} catch (err) {
const serializedError = miniSerializeError(err)
dispatch(rejected(serializedError, requestId, args))
// Rethrow this so the user can handle if desired
throw err
finalAction = rejected(serializedError, requestId, args)
}

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

actionCreator.pending = pending
actionCreator.rejected = rejected
actionCreator.fulfilled = fulfilled

return actionCreator
return Object.assign(actionCreator, { pending, rejected, fulfilled })
}
31 changes: 24 additions & 7 deletions type-tests/files/createAsyncThunk.typetest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createAsyncThunk, Dispatch, createReducer } from 'src'
import { createAsyncThunk, Dispatch, createReducer, AnyAction } from 'src'
import { ThunkDispatch } from 'redux-thunk'

function expectType<T>(t: T) {
Expand All @@ -7,20 +7,37 @@ function expectType<T>(t: T) {
function fn() {}

// basic usage
{
const dispatch = fn as ThunkDispatch<any, any, any>
;(async function() {
const dispatch = fn as ThunkDispatch<{}, any, AnyAction>

const async = createAsyncThunk('test', (id: number) =>
Promise.resolve(id * 2)
)
dispatch(async(3))

const reducer = createReducer({}, builder =>
builder
.addCase(async.pending, (_, action) => {})
.addCase(async.pending, (_, action) => {
expectType<ReturnType<typeof async['pending']>>(action)
})
.addCase(async.fulfilled, (_, action) => {
expectType<ReturnType<typeof async['fulfilled']>>(action)
expectType<number>(action.payload)
})
.addCase(async.rejected, (_, action) => {})
.addCase(async.rejected, (_, action) => {
expectType<ReturnType<typeof async['rejected']>>(action)
expectType<Error>(action.error)
})
)
}

const result = await dispatch(async(3))

if (async.fulfilled.match(result)) {
expectType<ReturnType<typeof async['fulfilled']>>(result)
// typings:expect-error
expectType<ReturnType<typeof async['rejected']>>(result)
} else {
expectType<ReturnType<typeof async['rejected']>>(result)
// typings:expect-error
expectType<ReturnType<typeof async['fulfilled']>>(result)
}
})()

0 comments on commit 5f7afe9

Please sign in to comment.