diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md index 8b19b879d3..0e133a1523 100644 --- a/etc/redux-toolkit.api.md +++ b/etc/redux-toolkit.api.md @@ -112,6 +112,7 @@ export function createAsyncThunk> & { abort: (reason?: string | undefined) => void; + getStatus: () => "pending" | "rejected" | "fulfilled"; }) & { pending: ActionCreatorWithPreparedPayload<[string, ThunkArg], undefined, string, never, { arg: ThunkArg; diff --git a/src/createAsyncThunk.test.ts b/src/createAsyncThunk.test.ts index c1a5c150a0..972f854c8d 100644 --- a/src/createAsyncThunk.test.ts +++ b/src/createAsyncThunk.test.ts @@ -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', () => { diff --git a/src/createAsyncThunk.ts b/src/createAsyncThunk.ts index f7f2f13835..94d5958cd5 100644 --- a/src/createAsyncThunk.ts +++ b/src/createAsyncThunk.ts @@ -201,6 +201,7 @@ If you want to use the AbortController to react to \`abort\` events, please cons getState: () => GetState, extra: GetExtra ) => { + let status: 'pending' | 'rejected' | 'fulfilled' = 'pending' const requestId = nanoid() const abortController = new AC() @@ -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 try { + status = 'pending' dispatch(pending(requestId, arg)) finalAction = await Promise.race([ abortedPromise, @@ -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 @@ -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 }) } }