@@ -32,15 +32,32 @@ import { withPolling } from './polling-handler';
3232import { notifySubscribers } from './pubsub-manager' ;
3333import { addRevalidator } from './revalidator-manager' ;
3434
35+ const inFlightResponse = {
36+ isFetching : true ,
37+ data : null ,
38+ error : null ,
39+ } ;
40+
3541/**
36- * Request function to make HTTP requests with the provided URL and configuration .
42+ * Sends an HTTP request to the specified URL using the provided configuration and returns a typed response .
3743 *
38- * @param {string } url - Request URL
39- * @param {RequestConfig } reqConfig - Request config passed when making the request
40- * @throws {ResponseError } If the request fails or is cancelled
41- * @returns {Promise<FetchResponse<ResponseData, RequestBody, QueryParams, PathParams>> } Response Data
44+ * @typeParam ResponseData - The expected shape of the response data. Defaults to `DefaultResponse`.
45+ * @typeParam RequestBody - The type of the request payload/body. Defaults to `DefaultPayload`.
46+ * @typeParam QueryParams - The type of the query parameters. Defaults to `DefaultParams`.
47+ * @typeParam PathParams - The type of the path parameters. Defaults to `DefaultUrlParams`.
48+ *
49+ * @param url - The endpoint URL to which the request will be sent.
50+ * @param config - Optional configuration object for the request, including headers, method, body, query, and path parameters.
51+ *
52+ * @returns A promise that resolves to a `FetchResponse` containing the typed response data and request metadata.
53+ *
54+ * @example
55+ * ```typescript
56+ * const { data } = await fetchf<UserData>('/api/user', { method: 'GET' });
57+ * console.log(data);
58+ * ```
4259 */
43- export async function request <
60+ export async function fetchf <
4461 ResponseData = DefaultResponse ,
4562 RequestBody = DefaultPayload ,
4663 QueryParams = DefaultParams ,
@@ -55,7 +72,9 @@ export async function request<
5572 > | null = null ,
5673) : Promise < FetchResponse < ResponseData , RequestBody , QueryParams , PathParams > > {
5774 const sanitizedReqConfig = reqConfig ? sanitizeObject ( reqConfig ) : { } ;
58- const mergedConfig = mergeConfigs ( defaultConfig , sanitizedReqConfig ) ;
75+ const mergedConfig = reqConfig
76+ ? mergeConfigs ( defaultConfig , sanitizedReqConfig )
77+ : { ...defaultConfig } ;
5978 const fetcherConfig = buildConfig ( url , mergedConfig ) ;
6079
6180 let response : FetchResponse <
@@ -68,26 +87,28 @@ export async function request<
6887 const {
6988 timeout,
7089 cancellable,
90+ cacheKey,
7191 dedupeTime,
7292 cacheTime,
73- cacheKey,
7493 revalidateOnFocus,
7594 revalidateOnReconnect,
7695 pollingInterval = 0 ,
7796 } = mergedConfig ;
7897
79- let _cacheKey : string | null = null ;
80-
81- // Generate cache key if required
82- if (
98+ const needsCacheKey = ! ! (
8399 cacheKey ||
84- cacheTime ||
100+ timeout ||
85101 dedupeTime ||
102+ cacheTime ||
86103 cancellable ||
87- timeout ||
88104 revalidateOnFocus ||
89105 revalidateOnReconnect
90- ) {
106+ ) ;
107+
108+ let _cacheKey : string | null = null ;
109+
110+ // Generate cache key if required
111+ if ( needsCacheKey ) {
91112 _cacheKey = generateCacheKey ( fetcherConfig ) ;
92113 }
93114
@@ -132,7 +153,16 @@ export async function request<
132153 > ;
133154
134155 // The actual request logic as a function (one poll attempt, with retries)
135- const doRequestOnce = async ( ) => {
156+ const doRequestOnce = async ( isStaleRevalidation = false ) => {
157+ // If cache key is specified, we will handle optimistic updates
158+ // and mark the request as in-flight, so to catch "fetching" state.
159+ // This is useful for Optimistic UI updates (e.g., showing loading spinners).
160+ if ( _cacheKey && ! isStaleRevalidation ) {
161+ setCache ( _cacheKey , inFlightResponse ) ;
162+
163+ notifySubscribers ( _cacheKey , inFlightResponse ) ;
164+ }
165+
136166 let attempt = 0 ;
137167 let waitTime : number = delay || 0 ;
138168 const _retries = retries > 0 ? retries : 0 ;
@@ -142,7 +172,7 @@ export async function request<
142172 const url = fetcherConfig . url as string ;
143173
144174 // Add the request to the queue. Make sure to handle deduplication, cancellation, timeouts in accordance to retry settings
145- const controller = await markInFlight (
175+ const controller = markInFlight (
146176 _cacheKey ,
147177 url ,
148178 timeout ,
@@ -397,32 +427,18 @@ export async function request<
397427 ) ;
398428 } ;
399429
400- // If cache key is specified, wrap the request with in-flight management
401- const doRequestWithInFlight = _cacheKey
402- ? async ( ) => {
403- // Optimistic Updates: Reflect that a fetch is happening, so to catch "fetching" state. This can help e.g. with UI updates (e.g., showing loading spinners).
404- const inFlightResponse = {
405- isFetching : true ,
406- data : null ,
407- error : null ,
408- headers : null ,
409- } ;
410- setCache ( _cacheKey , inFlightResponse ) ;
411-
412- notifySubscribers ( _cacheKey , inFlightResponse ) ;
413-
414- return doRequestOnce ( ) ;
415- }
416- : doRequestOnce ;
417-
418430 // If polling is enabled, use withPolling to handle the request
419- const doRequestPromise = withPolling (
420- doRequestWithInFlight ,
421- pollingInterval ,
422- mergedConfig . shouldStopPolling ,
423- mergedConfig . maxPollingAttempts ,
424- mergedConfig . pollingDelay ,
425- ) ;
431+ const doRequestPromise = pollingInterval
432+ ? withPolling <
433+ FetchResponse < ResponseData , RequestBody , QueryParams , PathParams >
434+ > (
435+ doRequestOnce ,
436+ pollingInterval ,
437+ mergedConfig . shouldStopPolling ,
438+ mergedConfig . maxPollingAttempts ,
439+ mergedConfig . pollingDelay ,
440+ )
441+ : doRequestOnce ( ) ;
426442
427443 // If deduplication is enabled, store the in-flight promise immediately
428444 if ( _cacheKey ) {
@@ -432,7 +448,7 @@ export async function request<
432448
433449 addRevalidator (
434450 _cacheKey ,
435- doRequestWithInFlight ,
451+ doRequestOnce ,
436452 undefined ,
437453 mergedConfig . staleTime ,
438454 doRequestOnce ,
@@ -450,44 +466,23 @@ export async function request<
450466 * @param {ResponseError } error Error instance
451467 * @returns {boolean } True if request is aborted
452468 */
453- const isRequestCancelled = ( error : ResponseError ) : boolean => {
469+ function isRequestCancelled ( error : ResponseError ) : boolean {
454470 return error . name === ABORT_ERROR || error . name === CANCELLED_ERROR ;
455- } ;
471+ }
456472
457473/**
458474 * Logs messages or errors using the configured logger's `warn` method.
459475 *
460476 * @param {RequestConfig } reqConfig - Request config passed when making the request
461477 * @param {...(string | ResponseError<any>) } args - Messages or errors to log.
462478 */
463- const logger = (
479+ function logger (
464480 reqConfig : RequestConfig ,
465481 ...args : ( string | ResponseError ) [ ]
466- ) : void => {
482+ ) : void {
467483 const logger = reqConfig . logger ;
468484
469485 if ( logger && logger . warn ) {
470486 logger . warn ( ...args ) ;
471487 }
472- } ;
473-
474- /**
475- * Sends an HTTP request to the specified URL using the provided configuration and returns a typed response.
476- *
477- * @typeParam ResponseData - The expected shape of the response data. Defaults to `DefaultResponse`.
478- * @typeParam RequestBody - The type of the request payload/body. Defaults to `DefaultPayload`.
479- * @typeParam QueryParams - The type of the query parameters. Defaults to `DefaultParams`.
480- * @typeParam PathParams - The type of the path parameters. Defaults to `DefaultUrlParams`.
481- *
482- * @param url - The endpoint URL to which the request will be sent.
483- * @param config - Optional configuration object for the request, including headers, method, body, query, and path parameters.
484- *
485- * @returns A promise that resolves to a `FetchResponse` containing the typed response data and request metadata.
486- *
487- * @example
488- * ```typescript
489- * const { data } = await fetchf<UserData>('/api/user', { method: 'GET' });
490- * console.log(data);
491- * ```
492- */
493- export const fetchf = request ;
488+ }
0 commit comments