Oskar Persson
Oskar Persson

Reputation: 6753

Creating a fetch wrapper in Typescript

I'm trying to create a wrapper for fetch in TypeScript but I can't get the typings right:

export interface ResponsePromise extends Promise<Response> {
  arrayBuffer(): Promise<ArrayBuffer>;
  blob(): Promise<Blob>;
  formData(): Promise<FormData>;
  json<T>(): Promise<T>;
  text(): Promise<string>;
}

class Api {
  public get(url: string, params = {}): ResponsePromise {
    const body = new URLSearchParams(params).toString();
    return fetch(url, {method: 'GET', body});
  }
}

const foo = async () => {
  const api = new Api();
  await api.get('http://www.example.com').json<any>();
}

Typescript Playground

Upvotes: 1

Views: 3748

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074305

TypeScript already has typings for fetch that you can reuse:

  • RequestInfo for the input parameter (you've called it url, but fetch calls it input [MDN, spec], and it may not be a string)
  • RequestInit for the optional init parameter (you don't seem to be using that in your get)
  • Response for the response.

So:

class Api {
  public get(input: RequestInfo, params = {}): Promise<Response> {
    const body = new URLSearchParams(params).toString();
    return fetch(input, {method: 'GET', body});
  }
}

In a comment you've asked:

how do I allow the user to simply call await api.get().json()?

That's a very different question than a question about types. You'd probably still return Promise<Response>, but you'd implement the first-level then handler within get (I do this anyway, because the fetch API is flawed, encouraging lots of failures to handle errors as I describe here.) So for instance:

class Api {
  public async get(input: RequestInfo, params = {}): Promise<Response> {
    const body = new URLSearchParams(params).toString();
    const response = await fetch(input, {method: 'GET', body});
    if (!response.ok) {
        throw new Error("HTTP error " + response.status);
    }
    return response;
  }
}

Since Response implements Body, it has json() etc. on it. If you wanted, you could return Promise<Body> instead and return .body:

class Api {
  public async get(input: RequestInfo, params = {}): Promise<Body> {
    const body = new URLSearchParams(params).toString();
    const response = await fetch(input, {method: 'GET', body});
    if (!response.ok) {
        throw new Error("HTTP error " + response.status);
    }
    return response.body;
  }
}

...but I don't think that buys you anything.

Upvotes: 3

Related Questions