I wrote my server backend in PHP. Now I want to create the communication between frontend (typescript) and backend.
I would like to receive a standardized response for each of my API requests. Accordingly, each response from my server is structured like this:
type Response<T> = {
status: boolean,
statusCode: number | null,
statusText: string | null,
statusDetails: string | null,
data: T | null
};
Important: Since not all API requests return data (e.g. login
and logout
), the key data
can also be null.
If data is available, it should be of generic type T
. This type depends on the API request. Example:
type ApiInitData = {
user: {
online: boolean | null,
name: string | null,
userGroupName: string | null,
language: string | null,
decimals: number | null,
},
language: {
supportedLanguages: string[];
defaultLanguage: string
}
}
So if the API request was successful, I want to get an object of type Response<ApiInitData>
. In this case the key data
in Response<T>
is not null
of course.
My goal is to use a Typescript function that returns a Promise. In the .then
of the Promise I want to control the further processing (e.g. display error message).
Example:
Api.login(username, password).then((response) => {
if(response.status === true){
// Ok
}else{
// Some error has occurred.
console.log(response.statusCode);
console.log(response.statusText);
}
});
When an error occurs, the Promise should be forced into the Response<null>
type so that a standardized object is always returned in the end.
This code snippet is my attempt:
//
// Example for login
//
static login = (username: string, password: string) => {
const data = {
'api': 'login',
'username': username,
'password': password
};
return this.request<Response<null>>(data);
}
//
// Example for init data
//
static getInitData = () => {
const data = {
'api': 'getInitializationData'
};
return this.request<Response<ApiInitData | null>>(data);
}
//
// Standard request function for all API requests.
//
static request = <T>(data: object, options: RequestInit = {}, url: string | null = null) => {
const requestOptions = this.prepareRequest(data, options, url);
return fetch(requestOptions.url, requestOptions.options)
.then((response) => {
if (response.ok) {
return response.json() as Promise<T>;
} else {
const result: Response<null> = {
status: false,
statusCode: response.status,
statusText: response.statusText,
statusDetails: null,
data: null,
}
return new Promise<Response<null>>((resolve) => { resolve(result) });
}
}).catch((error) => {
const result: Response<null> = {
status: false,
statusCode: 503,
statusText: error,
statusDetails: null,
data: null,
}
return new Promise<Response<null>>((resolve) => { resolve(result) });
});
}
The typescript compiler throws this error message: The argument of type "(response: Response) => Promise | Promise<Response>" cannot be assigned to the parameter of type "(value: Response) => T | PromiseLike". (line 4 of my request function).
I've no idea how to fix it. So I've two questions:
- How can I fix the error ?
- Is my approach used a common solution to this problem? Or is a completely different approach currently recommended here?
CodePudding user response:
Concerning the first question: The function given to .then()
returns Promise<T>
in case of success but Promise<Response<null>>
in case of error. To just fix the error you could explicity state the desired types like
.then(function(response): Promise<T | Response<null>> { ...
But it looks like you want to return Promise<Response<T>>
in both cases (in order to evaluate response.status === true
as mentioned). The result of response.json()
would then need to be wrapped by a Response<T>
in the same way as the error.
Concerning the second question: The way you approach this here is a pattern known as Either Monad.
edit: Cleared up the answer about the possible options.
CodePudding user response:
After much trial and error, I have now found a working solution for me. I now return Promise.resolve
and force its type to T
.
return Promise.resolve(result as unknown as T);
static request = <T>(data: object, options: RequestInit = {}, url: string | null = null) => {
const requestOptions = this.prepareRequest(data, options, url);
return fetch(requestOptions.url, requestOptions.options)
.then((response) => {
if (response.ok) {
return response.json() as Promise<T>;
} else {
const result: Response<null> = {
status: false,
statusCode: response.status,
statusText: response.statusText,
statusDetails: null,
data: null,
}
return Promise.resolve(result as unknown as T);
}
}).catch((error) => {
const result: Response<null> = {
status: false,
statusCode: 503,
statusText: error,
statusDetails: error,
data: null,
}
console.log(error);
return Promise.resolve(result as unknown as T);
});
}