1

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

1
  • Regarding default fetch arguments, as per 2023, according to lib/dom.d.ts it is (input: RequestInfo | URL, init?: RequestInit). This is what I was looking for :) Commented Dec 22, 2023 at 14:09

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Yes but (and maybe I should have pointed this out in the question) how do I allow the user to simply call await api.get().json()?
@OskarPersson - That's a very different question than a question about types. I've updated the answer.
the examples requires me to write (await api.get('http://www.example.com')).json(); instead of simply await api.get('http://www.example.com').json(); Which I would like to avoid. Also, I can't require and set typing of the json response. Like I do in my example. Maybe I should rewrite my question.
@OskarPersson - True, it does. That's because there are two levels of promise (the response, and reading the body). The only way to actually do api.get(...).json() is to: 1. Implement json on the promise returned by the function, hiding an implementation detail. I wouldn't do that. 2. Pass in the response method you want to use and call it within get. 3. Have multiple functions (getJSON, getArrayBuffer, ...).
Somehow ky is able to do this but I can't figure out what it exactly is that they do to make it work
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.