Skip to content

Add getStatus to createAsyncThunk to return promise state #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions etc/redux-toolkit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig exten
aborted: boolean;
}, SerializedError>> & {
abort: (reason?: string | undefined) => void;
getStatus: () => "pending" | "rejected" | "fulfilled";
}) & {
pending: ActionCreatorWithPreparedPayload<[string, ThunkArg], undefined, string, never, {
arg: ThunkArg;
Expand Down
58 changes: 58 additions & 0 deletions src/createAsyncThunk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,64 @@ describe('createAsyncThunk', () => {
expect(errorAction.meta.requestId).toBe(generatedRequestId)
expect(errorAction.meta.arg).toBe(args)
})

it('returns the status of a fulfilled promise via getStatus()', async () => {
let store = configureStore({
reducer(store: AnyAction[] = []) {
return store
}
})

const args = 123
let generatedRequestId = ''

const longRunningAsyncThunk = createAsyncThunk(
'longRunning',
async (args: number, { requestId }) => {
generatedRequestId = requestId

await new Promise(resolve => setTimeout(resolve, 1000))
}
)

const promise = store.dispatch(longRunningAsyncThunk(args))
expect(promise.getStatus()).toEqual('pending')

const result = await promise
expect(promise.getStatus()).toEqual('fulfilled')
expect(result.type).toContain('fulfilled')
expect(result.meta.requestId).toBe(generatedRequestId)
expect(result.meta.arg).toBe(args)
})

it('returns the status of a rejected promise via getStatus()', async () => {
let store = configureStore({
reducer(store: AnyAction[] = []) {
return store
}
})

const args = 123
let generatedRequestId = ''

const errorThunk = createAsyncThunk(
'errorThunk',
async (args: number, { requestId }) => {
generatedRequestId = requestId

throw new Error('Panic!')
}
)

const promise = store.dispatch(errorThunk(args))
expect(promise.getStatus()).toEqual('pending')

const result = await promise
expect(promise.getStatus()).toEqual('rejected')
expect(result.type).toContain('rejected')
expect(result.meta.requestId).toBe(generatedRequestId)
expect(result.meta.arg).toBe(args)
})
})

describe('createAsyncThunk with abortController', () => {
Expand Down
11 changes: 10 additions & 1 deletion src/createAsyncThunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ If you want to use the AbortController to react to \`abort\` events, please cons
getState: () => GetState<ThunkApiConfig>,
extra: GetExtra<ThunkApiConfig>
) => {
let status: 'pending' | 'rejected' | 'fulfilled' = 'pending'
const requestId = nanoid()

const abortController = new AC()
Expand All @@ -217,9 +218,14 @@ If you want to use the AbortController to react to \`abort\` events, please cons
abortController.abort()
}

function getStatus() {
return status
}

const promise = (async function() {
let finalAction: ReturnType<typeof fulfilled | typeof rejected>
try {
status = 'pending'
dispatch(pending(requestId, arg))
finalAction = await Promise.race([
abortedPromise,
Expand All @@ -236,12 +242,15 @@ If you want to use the AbortController to react to \`abort\` events, please cons
})
).then(result => {
if (result instanceof RejectWithValue) {
status = 'rejected'
return rejected(null, requestId, arg, result.value)
}
status = 'fulfilled'
return fulfilled(result, requestId, arg)
})
])
} catch (err) {
status = 'rejected'
finalAction = rejected(err, requestId, arg)
}
// We dispatch the result action _after_ the catch, to avoid having any errors
Expand All @@ -252,7 +261,7 @@ If you want to use the AbortController to react to \`abort\` events, please cons
dispatch(finalAction)
return finalAction
})()
return Object.assign(promise, { abort })
return Object.assign(promise, { abort, getStatus })
}
}

Expand Down