I'm new to using typescript. I was wondering how to utilize discriminated union for function return. I have an async
function that calls 2 endpoints, and I want to correctly type the return value based on each api call results.
So far I managed to created these types:
interface GetDetailSuccess {
status: "success";
data: MovieDetailResult;
}
interface GetDetailFail {
status: "failed";
error: any;
}
interface GetCastSuccess {
status: "success";
data: MovieCastResult
}
interface GetCastFail {
status: "failed";
error: any;
}
type MovieDetail = GetDetailSuccess | GetDetailFail;
type MovieCast = GetCastSuccess | GetCastFail;
type ReturnValue = {
movieDetail: MovieDetail;
movieCast: MovieCast;
};
Here is the simplified version of the function that I managed to create so far:
export const getMovieDetailAndCast = async ():Promise<ReturnValue> => {
const movDet = {} as MovieDetail;
const movCas = {} as MovieCast;
await Promise.allSettled([
api.getMovieDetail(),
api.getMovieCast(),
])
.then((responses) => {
responses.forEach((res, index) => {
if (index === 0) {
if (res.status === "fulfilled") {
movDet.status = "success";
if (movDet.status === "success") {
movDet.data = res.value.data;
}
}
if (res.status === "rejected") {
movDet.status = "failed";
if (movDet.status === "failed") {
movDet.error = res.reason.response.data.status_message;
}
}
}
if (index === 1) {
if (res.status === "fulfilled") {
movCas.status = "success";
if (movCas.status === "success") {
movCas.data = res.value.data;
}
}
if (res.status === "rejected") {
movCas.status = "failed";
if (movCas.status === "failed") {
movCas.error = res.reason.response.data.status_message;
}
}
}
});
})
.catch((err) => {
console.error(err);
});
return {
movieDetail: movDet,
movieCast: movCas,
};
}
So far the IDE doesn't yell at me for any error, but I do wonder if what I am doing is correct. Especially the part on how to narrowing the type and the part where I assigned an empty objects using as
. Is there anything that I could to improve the coding above? Any feedback would be appreciated
CodePudding user response:
It's not clear what you want to achieve using different types for success and failure. Usually, the easiest way to get if it is a success or failure is checking if the error property is defined or if the status property is "success". I think you should narrow your types like so to reduce complexity:
export const enum GetStatus {
PENDING = "pending",
SUCCESS = "success",
FAILED = "failed"
}
export interface GetDetail {
status: GetStatus;
data: MovieDetailResult | undefined;
error: any;
}
export interface GetCast {
status: GetStatus;
data: MovieCastResult | undefined;
error: any;
}
export interface DetailAndCast {
movieDetail: GetDetail;
movieCast: GetCast;
};
Then, if you follow this suggestion you might want to keep track of your call with a "pending" status.
export const getMovieDetailAndCast: () => Promise<DetailAndCast> = async () => {
const movieDetail: GetDetail = {status: GetStatus.PENDING, data: undefined, error: undefined};
const movieCast: GetCast = {status: GetStatus.PENDING, data: undefined, error: undefined};
await Promise.all([
api.getMovieDetail(),
api.getMovieCast(),
])
.then(responses => {
responses.foreach((res, index) => {
const returnValue = index === 0 ? movieDetail : movieCast;
if (res.status === "fulfilled") {
returnValue.status = GetStatus.SUCCESS;
returnValue.data = res.value.data;
} else if (res.status === "rejected") {
returnValue.status = GetStatus.FAILED;
returnValue.error = res.reason.response.data.status_message;
}
});
})
.catch(err => console.error(err));
return {movieDetail, movieCast};
}