Reputation: 173
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
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);
})();
Upvotes: 4
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