Home > Mobile >  Using fetch in Typescript with data transformations
Using fetch in Typescript with data transformations

Time:12-07

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.

  • Related