I'm returning an AxiosResponse object from a call to axios.get
in a generic function. That object has the shape:
interface AxiosResponse<T = any, D = any> {
data: T;
...
}
The get
call has this type:
get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
I'm hours into digging for an answer to fix this error:
"Property 'data' does not exist on type 'Awaited<R>
In this code (error location marked):
export const fetchWithRetry = async <
T = any,
Q = Record<string, string>,
R = AxiosResponse<T>
>(
route: string,
queryParams: Partial<Q> = {},
) => {
const response = await fetchResponseWithRetry<T, Q, R>(route, queryParams)
return response.data; // <= ERROR IS HERE
};
export const fetchResponseWithRetry = async <
T = any,
Q = Record<string, string>,
R = AxiosResponse<T>,
>(
route: string,
queryParams: Partial<Q> = {},
) => {
try {
const options: AxiosRequestConfig = {
headers: {
"Content-Type": "application/json",
},
params: <Q>{ ...queryParams },
};
return (await axios.get<T, R, unknown>(route, options));
} catch (error) {
if (error instanceof Error) {
buildLogger(`${error.message}`);
if (error instanceof AxiosError) buildLogger(`${error.request.url}`);
}
}
throw new Error(`This should never be reached.`);
};
I understand that the Awaited<R>
type was added in 4.5. I understand from other answers that the Awaited type should be unwrapped to R (from the accepted answer on that question):
If the type
T
you are awaiting is aPromise<U>
whereU
isn't aPromise
of any kind, thenAwaited<T>
is the same asU
How do I fix this?
The problem here is that you're customisation of the R
type means the response could literally be anything so Typescript cannot guarantee it has a data
property.
There's really no need to provide generics for anything other than the T
response data type.
This code works without any errors and also does away with a few other small issues
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const fetchWithRetry = async <T = any,>(
route: string,
queryParams: Record<string, string> = {},
) => {
const response = await fetchResponseWithRetry<T>(route, queryParams);
return response.data;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const fetchResponseWithRetry = async <T = any,>(
route: string,
queryParams: Record<string, string> = {},
) => {
try {
// GET requests do not have a body so no content-type required
const options: AxiosRequestConfig = {
params: queryParams,
};
return await axios.get<T>(route, options);
} catch (error) {
if (error instanceof Error) {
buildLogger(`${error.message}`);
if (axios.isAxiosError(error)) {
// use the provided type guard method
buildLogger(`${error.request.url}`);
}
}
}
throw new Error(`This should never be reached.`);
};
If you really wanted to keep the R
generic, you should use extends
instead of a default value.
R extends { data: T } = AxiosResponse<T>