0

So I have a wrapper for api calls to a strapi server

export const api = {
    post: async<T extends unknown, K>(url: string, body: Partial<T>, jwt?: string): Promise<K> => {
        try {
            const result = await postData<Partial<T>, K>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

I am trying to get it so K is optional so I can do the follow

 await api.post<type1, type2>(url, body);
 await api.post<type1>(url, body);

I have tried

export const api = {
    post: async<T extends unknown, K = undefined>(url: string, body: Partial<T>, jwt?: string): Promise<K | T> => {
        try {
            const result = await postData<Partial<T>, K | T>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

but I would either get typing errors because it would be missing a field from type1 when the return type should only be type2 or I would get that the return object could possibly undefined.

I was wondering if it is possible to have it so if both types are used for the post function, it will use the second type as the return type or use the first type as the return type?

Full Example that can be pasted into typescript playground with comments where the errors occurred

const api = {
    post: async<T extends unknown, K = undefined>(url: string, body: Partial<T>, jwt?: string): Promise<K | T> => {
        try {
            const result = await postData<Partial<T>, K | T>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

function postData<K, T>(url: string, data: K, jwt: string = '', failOnNotOk: boolean = true): T {
    const request: T = (data) as any;

    return request;
}

type user = {
    email: string;
    password: string;
}

type res = {
    valid: string;
}
(async () => {
    const url: string = 'https://google.com';
    const body: user = {
        email: 'test@example.com',
        password: 'test1234',
    };
    // this gives an error about result having the possibility of being undefined
    const result = await api.post<user>(url, body);
    console.log(result.email);

    // returns an errror about valid not being a field on user when the return type should only be res
    const res = await api.post<user, res>(url, body);
    console.log(res.valid);
})();
  • Please consider editing the code so as to constitute a [mcve] as described in [ask]. Ideally someone could drop the code into a standalone IDE like [the TypeScript Playground](https://www.typescriptlang.org/play/) and demonstrate the issue you're seeing. You should also be clear about specific errors along with specific use cases and what you expect to see vs what you're actually seeing. Maybe you just want `async(...)` (that is, have `K` default to `T`), but I can't really tell without more clarity in the question. Good luck! – jcalz Jan 02 '20 at 18:49
  • 1
    Added a full example at the end of the question that can be pasted into typescript playground –  Jan 02 '20 at 19:32
  • I haven't done this myself, but I have seen it in the [typings](https://github.com/axios/axios/blob/master/index.d.ts) for axios. They use `export interface AxiosPromise extends Promise> { }`. – ps2goat Jan 02 '20 at 19:56

1 Answers1

0

With the example as given, I'd probably change api.post to this:

const api = {
    post: async<T extends unknown, U = T>(
      url: string, 
      body: Partial<T>, 
      jwt?: string
    ): Promise<U> => {
        try {
            const result = await postData<Partial<T>, U>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

(I changed K to U because the name K usually implies key-like properties assignable to keyof any). Here the return type will just be Promise<U>, and U will default to T if not specified. That gives the following behavior:

const result = await api.post<user>(url, body);
console.log(result.email); // okay
const res = await api.post<user, res>(url, body);
console.log(res.valid); // okay

which I think you want. Note that it's a bit annoying to need to specify user explicitly instead of just having the compiler infer it from the type of body, but if you don't mind doing it then that's fine. TypeScript lacks direct support for partial type parameter inference, so there's no way to just have the compiler infer T properly if you'd also like to specify U explicitly, and the workarounds I know about aren't necessarily better than just manually specifying T yourself.

Anyway, hope that helps; good luck!

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360