diff --git a/packages/toolkit/src/createAsyncThunk.ts b/packages/toolkit/src/createAsyncThunk.ts index ef4f5f1977..c6b1517b3b 100644 --- a/packages/toolkit/src/createAsyncThunk.ts +++ b/packages/toolkit/src/createAsyncThunk.ts @@ -11,6 +11,7 @@ import type { Id, IsAny, IsUnknown, + SafePromise, TypeGuard, } from './tsHelpers' import { nanoid } from './nanoid' @@ -242,7 +243,7 @@ export type AsyncThunkAction< dispatch: GetDispatch, getState: () => GetState, extra: GetExtra -) => Promise< +) => SafePromise< | ReturnType> | ReturnType> > & { @@ -676,7 +677,7 @@ export const createAsyncThunk = /* @__PURE__ */ (() => { } return finalAction })() - return Object.assign(promise as Promise, { + return Object.assign(promise as SafePromise, { abort, requestId, arg, diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 43ac586292..1c8d41af43 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -203,6 +203,6 @@ export { combineSlices } from './combineSlices' export type { WithSlice } from './combineSlices' -export type { ExtractDispatchExtensions as TSHelpersExtractDispatchExtensions } from './tsHelpers' +export type { ExtractDispatchExtensions as TSHelpersExtractDispatchExtensions, SafePromise } from './tsHelpers' export { formatProdErrorMessage } from './formatProdErrorMessage' diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 1b3a1e0aa1..ae761f3855 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -21,6 +21,8 @@ import type { QueryResultSelectorResult } from './buildSelectors' import type { Dispatch } from 'redux' import { isNotNullish } from '../utils/isNotNullish' import { countObjectKeys } from '../utils/countObjectKeys' +import type { SafePromise } from '../../tsHelpers' +import { asSafePromise } from '../../tsHelpers' declare module './module' { export interface ApiEndpointQuery< @@ -60,7 +62,7 @@ type StartQueryActionCreator< export type QueryActionCreatorResult< D extends QueryDefinition -> = Promise> & { +> = SafePromise> & { arg: QueryArgFrom requestId: string subscriptionOptions: SubscriptionOptions | undefined @@ -90,7 +92,7 @@ type StartMutationActionCreator< export type MutationActionCreatorResult< D extends MutationDefinition -> = Promise< +> = SafePromise< | { data: ResultTypeFrom } | { error: @@ -335,7 +337,7 @@ You must add the middleware for RTK-Query to function correctly!` const selectFromState = () => selector(getState()) const statePromise: QueryActionCreatorResult = Object.assign( - forceQueryFn + (forceQueryFn ? // a query has been forced (upsertQueryData) // -> we want to resolve it once data has been written with the data that will be written thunkResult.then(selectFromState) @@ -345,7 +347,9 @@ You must add the middleware for RTK-Query to function correctly!` Promise.resolve(stateAfter) : // query just started or one is already in flight // -> wait for the running query, then resolve with data from after that - Promise.all([runningQuery, thunkResult]).then(selectFromState), + Promise.all([runningQuery, thunkResult]).then( + selectFromState + )) as SafePromise, { arg, requestId, @@ -421,10 +425,10 @@ You must add the middleware for RTK-Query to function correctly!` const thunkResult = dispatch(thunk) middlewareWarning(dispatch) const { requestId, abort, unwrap } = thunkResult - const returnValuePromise = thunkResult - .unwrap() - .then((data) => ({ data })) - .catch((error) => ({ error })) + const returnValuePromise = asSafePromise( + thunkResult.unwrap().then((data) => ({ data })), + (error) => ({ error }) + ) const reset = () => { dispatch(removeMutationResult({ requestId, fixedCacheKey })) diff --git a/packages/toolkit/src/tsHelpers.ts b/packages/toolkit/src/tsHelpers.ts index bd42de858e..d4d6a2f3e0 100644 --- a/packages/toolkit/src/tsHelpers.ts +++ b/packages/toolkit/src/tsHelpers.ts @@ -207,3 +207,21 @@ export type Tail = T extends [any, ...infer Tail] : never export type UnknownIfNonSpecific = {} extends T ? unknown : T + +/** + * A Promise that will never reject. + * @see https://github.com/reduxjs/redux-toolkit/issues/4101 + */ +export type SafePromise = Promise & { + __linterBrands: 'SafePromise' +} + +/** + * Properly wraps a Promise as a {@link SafePromise} with .catch(fallback). + */ +export function asSafePromise( + promise: Promise, + fallback: (error: unknown) => Rejected +) { + return promise.catch(fallback) as SafePromise +}