Im trying convert js code to ts, аnd i have this:
function api<T>(url: string): Promise<T> {
return fetch(url)
.then((res) => {
return res.json().then((resJson: T) => ({
ok: res.ok,
status: res.status,
body: resJson,
}));
})
.then((res) => {
if (res.ok) {
return res.body;
}
return Promise.reject({
status: res.status,
message: res.body.message,
});
});
}
I dont know how to solve the problem with
message: res.body.message // Property 'message' does not exist on type 'T'
My body-response contains the "message" property as an optional only if res.ok === false. How to solve this case? Approximate usage:
type ResBody = {
success: boolean;
message?: string;
data?: string[];
};
api<ResBody>("https://example.com")
.then(({ success, data }) => {
console.log(success, data);
})
.catch((err) => {
console.log(err.message, err.status)
});
CodePudding user response:
The Promise type has a generic parameter T so that you can declare the return type of the async function. This allows you to reference that return value in a type-safe way.
Your first then returns an object with body assigned a value of type T, and then your next then takes it as its res input. This means that res is inferred to have a body of type T. You then reference res.body.message, but no where have you declared that type T is a type with a field message.
You need to do that when you declare the generic parameter T in the function api<T> declaration as follows:
function api<T extends ResBody>(url: string): Promise<T> {
return fetch(url)
.then((res) => {
return res.json().then((resJson: T) => ({
ok: res.ok,
status: res.status,
body: resJson,
}));
})
.then((res) => {
if (res.ok) {
return res.body;
}
return Promise.reject({
status: res.status,
message: res.body.message,
});
});
}
T extends ResBody tells Typescript that the generic type T, whatever it ends up being, will be a ResBody or a subtype of it, and thus will have an optional message field of type string.
You can be even more generic than that, i.e. if T can be any type that contains a message field as follows:
function api<T extends {message?: string}>(url: string): Promise<T> {
return fetch(url)
.then((res) => {
return res.json().then((resJson: T) => ({
ok: res.ok,
status: res.status,
body: resJson,
}));
})
.then((res) => {
if (res.ok) {
return res.body;
}
return Promise.reject({
status: res.status,
message: res.body.message,
});
});
}
T extends {message?: string} tells Typescript that the generic type T, whatever it ends up being, has an optional message field of type string.
