cossacksman
cossacksman

Reputation: 173

Typescript Fetch API return Promise<T> or Promise<T[]>

I'm writing an API wrapper in Typescript and I'm looking at using the Fetch API with Promises. I want to use generics in the request method so that I can model bind to the type provided however I'm fairly new to Typescript (and promises, for that matter) and cannot find anything on conditionally returning Promise<T> or Promise<T[]>, since not all requests will return an array.

This is the state of my code, so far, it's not in a finished state but it's workable.

export default class ApiWrapper
{
    public async request<T>(endpoint: string, method: string = 'GET', parameters: object = {}) : Promise<void|T>
    {
        var protocol = process.env.REACT_APP_APPLICATION_PROTOCOL || 'https'
        var host = process.env.REACT_APP_APPLICATION_HOST
        var port = process.env.REACT_APP_APPLICATION_PORT ? `:${process.env.REACT_APP_APPLICATION_PORT}` : ''
        var path = process.env.REACT_APP_APPLICATION_PATH || '/'

        if (!host)
        {
            // TODO:- Handle error
        }

        let body = JSON.stringify(parameters)

        return await fetch(`${protocol}://${host}${port}${path}/${endpoint}`, {
            method: method,
            body: body
        })
        .then(response => response.json() as Promise<T>)
        .then(data => {
            return data
        })
    }

    public async get<T>(endpoint: string, parameters: object = {})
    {
        return await this.request<T>(endpoint, 'GET', parameters)
    }

    public async post(endpoint: string, parameters: object)
    {
        return await this.request(endpoint, 'POST', parameters)
    }

    public async put(endpoint: string, parameters: object)
    {
        return await this.request(endpoint, 'PUT', parameters)
    }

    public async delete(endpoint: string)
    {
        return await this.request(endpoint, 'DELETE')
    }
}

I can try to conditionally return like so:

return await fetch(`${protocol}://${host}${port}${path}/${endpoint}`, {
    method: method,
    body: body
})
.then(response => {
    if (Array.isArray(response.json())) {
        return response.json() as Promise<T[]>
    } else {
        return response.json() as Promise<T>
    }
})
.then(data => {
    return data
})

Although T is not assignable to type T[]. This may be really simple, silly or not even possible but any pointers would be appreciated. Any additional information can be provided if there's

Upvotes: 4

Views: 10354

Answers (2)

Mosh Feu
Mosh Feu

Reputation: 29277

You don't need to return the same result with a different signature. T can be not only single type (such as string, number, etc.) but also an array of a type.

Also, I don't see where the function is void. If you meant to the case if throwing an error, the right signature is never.

So, the function can look as the follow:

class ApiWrapper {
  public async request<T>(endpoint: string, method: string = "GET", parameters: object = {}): Promise<never | T> {
    let protocol, host, port, path;
    let body = JSON.stringify(parameters);

    return await fetch(`${protocol}://${host}${port}${path}/${endpoint}`, {
      method: method,
      body: body
    })
      .then(response => {
        if (response.ok) {
          return response.json();
        } else {
          throw new Error(response.statusText);
        }
      })
      .then(data => {
        return data;
      });
  }
}

(async () => {
  const apiWrapper = new ApiWrapper();
  const singleItem: string = await apiWrapper.request<string>("asdfsd");
  console.log(singleItem.endsWith);

  const arrayItemWrong: string[] = await apiWrapper.request<string>("asdfsd");
  const arrayItem: string[] = await apiWrapper.request<string[]>("asdfsd");
  console.log(arrayItem.concat);
})();

Playground

Upvotes: 4

cossacksman
cossacksman

Reputation: 173

Okay, I think I figured it out, although I'm sure this could likely be shortened somewhat.

If I declare the return type of the Promise in the .then() callback by not using an arrow function, like so:

return await fetch(`${protocol}://${host}${port}${path}/${endpoint}`, {
    method: method,
    body: body
})
.then(function(response): Promise<T|T[]> {
    if (response.ok) {
        if (Array.isArray(response.json())) {
            return response.json() as Promise<T[]>
        } else {
            return response.json() as Promise<T>
        }
    } else {
        throw new Error(response.statusText)
    }
})
.then(data => {
    return data
})

Then the compiler seems to accept this. It likely should have been obvious but TypeScript compiler errors are a maze.

Upvotes: 0

Related Questions