Skip to content

Commit 8c38f92

Browse files
author
ben.durrant
committed
use promiseWithResolvers for RTKQ lifecycle management
1 parent b65f6b5 commit 8c38f92

File tree

4 files changed

+100
-75
lines changed

4 files changed

+100
-75
lines changed

packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts

+16-13
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import type { PatchCollection, Recipe } from '../buildThunks'
1313
import type {
1414
ApiMiddlewareInternalHandler,
1515
InternalHandlerBuilder,
16-
PromiseWithKnownReason,
1716
SubMiddlewareApi,
1817
} from './types'
18+
import type { PromiseWithKnownReason } from '../../utils/promiseWithResolvers'
19+
import { promiseWithResolvers } from '../../utils/promiseWithResolvers'
1920

2021
export type ReferenceCacheLifecycle = never
2122

@@ -274,20 +275,22 @@ export const buildCacheLifecycleHandler: InternalHandlerBuilder = ({
274275

275276
let lifecycle = {} as CacheLifecycle
276277

277-
const cacheEntryRemoved = new Promise<void>((resolve) => {
278-
lifecycle.cacheEntryRemoved = resolve
279-
})
280-
const cacheDataLoaded: PromiseWithKnownReason<
278+
let cacheEntryRemoved: Promise<void>
279+
;({ promise: cacheEntryRemoved, resolve: lifecycle.cacheEntryRemoved } =
280+
promiseWithResolvers<void>())
281+
282+
const {
283+
promise: cacheDataLoaded,
284+
resolve,
285+
reject,
286+
} = promiseWithResolvers<
281287
{ data: unknown; meta: unknown },
282288
typeof neverResolvedError
283-
> = Promise.race([
284-
new Promise<{ data: unknown; meta: unknown }>((resolve) => {
285-
lifecycle.valueResolved = resolve
286-
}),
287-
cacheEntryRemoved.then(() => {
288-
throw neverResolvedError
289-
}),
290-
])
289+
>()
290+
lifecycle.valueResolved = resolve
291+
292+
cacheEntryRemoved.then(() => reject(neverResolvedError), reject)
293+
291294
// prevent uncaught promise rejections from happening.
292295
// if the original promise is used in any way, that will create a new promise that will throw again
293296
cacheDataLoaded.catch(() => {})

packages/toolkit/src/query/core/buildMiddleware/queryLifecycle.ts

+17-15
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import { DefinitionType } from '../../endpointDefinitions'
88
import type { QueryFulfilledRejectionReason } from '../../endpointDefinitions'
99
import type { Recipe } from '../buildThunks'
1010
import type {
11-
PromiseWithKnownReason,
12-
PromiseConstructorWithKnownReason,
1311
InternalHandlerBuilder,
1412
ApiMiddlewareInternalHandler,
1513
} from './types'
14+
import type {
15+
PromiseWithKnownReason,
16+
PromiseWithResolvers,
17+
} from '../../utils/promiseWithResolvers'
18+
import { promiseWithResolvers } from '../../utils/promiseWithResolvers'
1619

1720
export type ReferenceQueryLifecycle = never
1821

@@ -211,10 +214,14 @@ export const buildQueryLifecycleHandler: InternalHandlerBuilder = ({
211214
const isRejectedThunk = isRejected(queryThunk, mutationThunk)
212215
const isFullfilledThunk = isFulfilled(queryThunk, mutationThunk)
213216

214-
type CacheLifecycle = {
215-
resolve(value: { data: unknown; meta: unknown }): unknown
216-
reject(value: QueryFulfilledRejectionReason<any>): unknown
217-
}
217+
type CacheLifecycle = Omit<
218+
PromiseWithResolvers<
219+
{ data: unknown; meta: unknown },
220+
QueryFulfilledRejectionReason<any>
221+
>,
222+
'promise'
223+
>
224+
218225
const lifecycleMap: Record<string, CacheLifecycle> = {}
219226

220227
const handler: ApiMiddlewareInternalHandler = (action, mwApi) => {
@@ -226,15 +233,10 @@ export const buildQueryLifecycleHandler: InternalHandlerBuilder = ({
226233
const endpointDefinition = context.endpointDefinitions[endpointName]
227234
const onQueryStarted = endpointDefinition?.onQueryStarted
228235
if (onQueryStarted) {
229-
const lifecycle = {} as CacheLifecycle
230-
const queryFulfilled =
231-
new (Promise as PromiseConstructorWithKnownReason)<
232-
{ data: unknown; meta: unknown },
233-
QueryFulfilledRejectionReason<any>
234-
>((resolve, reject) => {
235-
lifecycle.resolve = resolve
236-
lifecycle.reject = reject
237-
})
236+
const { promise: queryFulfilled, ...lifecycle } = promiseWithResolvers<
237+
{ data: unknown; meta: unknown },
238+
QueryFulfilledRejectionReason<any>
239+
>()
238240
// prevent uncaught promise rejections from happening.
239241
// if the original promise is used in any way, that will create a new promise that will throw again
240242
queryFulfilled.catch(() => {})

packages/toolkit/src/query/core/buildMiddleware/types.ts

-47
Original file line numberDiff line numberDiff line change
@@ -83,50 +83,3 @@ export type ApiMiddlewareInternalHandler<Return = void> = (
8383
export type InternalHandlerBuilder<ReturnType = void> = (
8484
input: BuildSubMiddlewareInput
8585
) => ApiMiddlewareInternalHandler<ReturnType>
86-
87-
export interface PromiseConstructorWithKnownReason {
88-
/**
89-
* Creates a new Promise with a known rejection reason.
90-
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
91-
* a resolve callback used to resolve the promise with a value or the result of another promise,
92-
* and a reject callback used to reject the promise with a provided reason or error.
93-
*/
94-
new <T, R>(
95-
executor: (
96-
resolve: (value: T | PromiseLike<T>) => void,
97-
reject: (reason?: R) => void
98-
) => void
99-
): PromiseWithKnownReason<T, R>
100-
}
101-
102-
export interface PromiseWithKnownReason<T, R>
103-
extends Omit<Promise<T>, 'then' | 'catch'> {
104-
/**
105-
* Attaches callbacks for the resolution and/or rejection of the Promise.
106-
* @param onfulfilled The callback to execute when the Promise is resolved.
107-
* @param onrejected The callback to execute when the Promise is rejected.
108-
* @returns A Promise for the completion of which ever callback is executed.
109-
*/
110-
then<TResult1 = T, TResult2 = never>(
111-
onfulfilled?:
112-
| ((value: T) => TResult1 | PromiseLike<TResult1>)
113-
| undefined
114-
| null,
115-
onrejected?:
116-
| ((reason: R) => TResult2 | PromiseLike<TResult2>)
117-
| undefined
118-
| null
119-
): Promise<TResult1 | TResult2>
120-
121-
/**
122-
* Attaches a callback for only the rejection of the Promise.
123-
* @param onrejected The callback to execute when the Promise is rejected.
124-
* @returns A Promise for the completion of the callback.
125-
*/
126-
catch<TResult = never>(
127-
onrejected?:
128-
| ((reason: R) => TResult | PromiseLike<TResult>)
129-
| undefined
130-
| null
131-
): Promise<T | TResult>
132-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { safeAssign } from "../tsHelpers";
2+
3+
export interface PromiseConstructorWithKnownReason {
4+
/**
5+
* Creates a new Promise with a known rejection reason.
6+
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
7+
* a resolve callback used to resolve the promise with a value or the result of another promise,
8+
* and a reject callback used to reject the promise with a provided reason or error.
9+
*/
10+
new <T, R>(
11+
executor: (
12+
resolve: (value: T | PromiseLike<T>) => void,
13+
reject: (reason?: R) => void
14+
) => void
15+
): PromiseWithKnownReason<T, R>
16+
}
17+
18+
export interface PromiseWithKnownReason<T, R>
19+
extends Omit<Promise<T>, 'then' | 'catch'> {
20+
/**
21+
* Attaches callbacks for the resolution and/or rejection of the Promise.
22+
* @param onfulfilled The callback to execute when the Promise is resolved.
23+
* @param onrejected The callback to execute when the Promise is rejected.
24+
* @returns A Promise for the completion of which ever callback is executed.
25+
*/
26+
then<TResult1 = T, TResult2 = never>(
27+
onfulfilled?:
28+
| ((value: T) => TResult1 | PromiseLike<TResult1>)
29+
| undefined
30+
| null,
31+
onrejected?:
32+
| ((reason: R) => TResult2 | PromiseLike<TResult2>)
33+
| undefined
34+
| null
35+
): Promise<TResult1 | TResult2>
36+
37+
/**
38+
* Attaches a callback for only the rejection of the Promise.
39+
* @param onrejected The callback to execute when the Promise is rejected.
40+
* @returns A Promise for the completion of the callback.
41+
*/
42+
catch<TResult = never>(
43+
onrejected?:
44+
| ((reason: R) => TResult | PromiseLike<TResult>)
45+
| undefined
46+
| null
47+
): Promise<T | TResult>
48+
}
49+
50+
export const PromiseWithKnownReason = Promise as PromiseConstructorWithKnownReason
51+
52+
53+
type PromiseExecutor<T, R> = ConstructorParameters<typeof PromiseWithKnownReason<T, R>>[0];
54+
55+
export type PromiseWithResolvers<T, R> = {
56+
promise: PromiseWithKnownReason<T, R>;
57+
resolve: Parameters<PromiseExecutor<T, R>>[0];
58+
reject: Parameters<PromiseExecutor<T, R>>[1];
59+
};
60+
61+
export const promiseWithResolvers = <T, R = unknown>(): PromiseWithResolvers<T, R> => {
62+
const result = {} as PromiseWithResolvers<T, R>;
63+
result.promise = new PromiseWithKnownReason<T, R>((resolve, reject) => {
64+
safeAssign(result, { reject, resolve });
65+
});
66+
return result;
67+
};

0 commit comments

Comments
 (0)