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
.