If you are confident that a value belongs to a specific type, but the compiler is unable to confirm and raises an error, you can utilize a type assertion to suppress its warning. (In some cases, when the asserted type is vastly different from what the compiler expects, an intermediate type assertion may be needed. So if foo as Bar
doesn't work, you can resort to foo as any as Bar
instead.)
In the context of your code, this would look like:
function handleGetPagePropsErrors<T extends GetGenericProps>(
wrappedHandler: T,
): T {
return (async (context: any) => {
try {
return await wrappedHandler(context);
} catch (err) {
if (err instanceof AppError) {
return {
props: {
message: err.message,
type: err.type,
},
};
} else {
throw err; // let Next.js handle it
}
}
}) as T; // <-- assertion here
}
It's important to note that by utilizing type assertions, you take on the responsibility for ensuring type safety at runtime rather than compile time. If the type assertion turns out to be incorrect, runtime issues may arise without prior compiler warnings. Proceed with caution.
Furthermore, the generic type T extends GetGenericProps
could be more specific than just one of the union elements. For instance, TypeScript allows for setting "expando" properties on functions, as illustrated below:
const gssp = async ({ req, res, params }: GetServerSidePropsContext) => {
if (Math.random() > 0.5) {
throw new AppError(
ErrorType.BAD_THINGS_HAPPEN,
"Sometimes code just doesn't work, dude"
);
}
return {
props: {
foo: 'bar'
},
};
};
gssp.expandoProp = "oops";;
Therefore, while gssp
is considered a GetServerSideProps
, it additionally possesses a string
-valued expandoProp
property. This results in a type such as
GetServerSideProps & {expandoProp: string}
. Consequently, the output of
handleGetPagePropsErrors(gssp)
is expected to also have such a property:
const getServerSideProps = handleGetPagePropsErrors(gssp);
getServerSideProps.expandoProp.toUpperCase(); // permissible?
// No compiler error, potential runtime issue
However, in reality, the implementation of handleGetPagePropsErrors()
does not exactly match the input type but a related type. Thus, technically, as T
was misrepresented.
The likelihood of encountering such odd scenarios is slim in practice, yet awareness of these intricacies and judicious use of type assertions is advised.
Another approach involves embracing slightly easier-to-guarantee types (albeit shifting some of the type safety burden from compiler to coder) and configuring handleGetPagePropsErrors()
as an overloaded function.
TypeScript permits declaration of multiple distinct call signatures for a function along with a single implementation catering to all signatures. While the compiler's validation is less stringent for such implementations, misrepresenting the return type remains plausible. Nevertheless, restricting the potential output types to solely GetStaticProps
or
GetServerSideProps</code, instead of any generic subtype of <code>GetGenericProps
, becomes feasible.
Here's how you could implement this:
function handleGetPagePropsErrors(wrappedHandler: GetStaticProps): GetStaticProps;
function handleGetPagePropsErrors(wrappedHandler: GetServerSideProps): GetServerSideProps;
function handleGetPagePropsErrors(wrappedHandler: GetGenericProps): GetGenericProps {
return (async (context: any) => {
try {
return await wrappedHandler(context);
} catch (err) {
if (err instanceof AppError) {
return {
props: {
message: err.message,
type: err.type,
},
};
} else {
throw err; // let Next.js handle it
}
}
});
}
Consequently, the preceding issue concerning expando properties no longer persists as the function does not purport to provide something more specific than simply GetServerSideProps
:
const getServerSideProps = handleGetPagePropsErrors(gssp);
// getServerSideProps: GetServerSideProps
getServerSideProps.expandoProp.toUpperCase(); // error!
// Property 'expandoProp' does not exist on type 'GetServerSideProps'
Playground link to code