Uniform retrieval function for interaction with API

I have developed my server backend using PHP and now I am looking to establish communication between the frontend (typescript) and backend.

For each of my API requests, I desire to receive a standardized response. Hence, every response from the server follows this structure:

type Response<T> = {
    status: boolean,
    statusCode: number | null,
    statusText: string | null,
    statusDetails: string | null,
    data: T | null
};

Note: Some API requests may not return any data like login and logout, in which case the key data can be null as well. If the data is returned, it should adhere to the generic type T associated with the specific API request. For example:

type ApiInitData = {
    user: {
        online: boolean | null,
        name: string | null,
        userGroupName: string | null,
        language: string | null,
        decimals: number | null,
    },
    language: {
        supportedLanguages: string[];
        defaultLanguage: string
    }
}

Upon successful completion of an API request, I expect to receive an object of type Response<ApiInitData>. In such cases, the key data in Response<T> will not be null.

My objective is to create a Typescript function that returns a Promise. Within the .then block of the Promise, I aim to control the subsequent actions, such as displaying an error message. Example:

Api.login(username, password).then((response) => {
    if(response.status === true){
        // Proceed with the operation
    }else{
        // An error has occurred
        console.log(response.statusCode);
        console.log(response.statusText);
    }
});

In case of an error, the Promise should be coerced into the Response<null> type to ensure consistency in the returned object format. The following code snippet illustrates my approach:

//
// Login Example
//
static login = (username: string, password: string) => {
        const data = {
            'api': 'login',
            'username': username,
            'password': password
        };
        return this.request<Response<null>>(data);
    }

//
// Initialization Data Example
//
static getInitData = () => {
        const data = {
            'api': 'getInitializationData'
        };
        return this.request<Response<ApiInitData | null>>(data);
    }

//
// Universal request function for all API calls
//
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 is throwing an 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 am unsure how to resolve this issue, so I have two questions:

  • How can I rectify the error?
  • Is my approach a commonly used solution to this problem? Or is there a different recommended approach currently prevalent in this scenario?

Answer №1

Regarding the initial query: The .then() function returns Promise<T> upon success and

Promise<Response<null>>
upon encountering an error. To rectify this issue, you can explicitly define the desired types as shown below:

.then(function(response): Promise<T | Response<null>> { ...

However, based on your context, it seems like you intend to return Promise<Response<T>> for both scenarios (as indicated by the need to assess response.status === true). This would necessitate wrapping the result of response.json() in a Response<T> similar to how errors are handled.

In response to the follow-up question: Your approach aligns with a concept known as the Either Monad, which offers a functional method for error management in JavaScript.

edit: Clarified the explanation regarding potential options available.

Answer №2

Finally, after numerous attempts and experimenting, I have managed to discover a viable solution that works perfectly for me. My approach now involves utilizing Promise.resolve and explicitly specifying its type as 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);
            });
    }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Why does the playwright's onEnd() results not include the duration as specified in the documentation? What am I overlooking?

The built-in onEnd method can have a results object that is accessible within the function. According to the documentation here, this object should include the property duration, which represents the time in milliseconds. However, when I attempt to access ...

The concept of a singleton design pattern is like a hidden treasure waiting to be

My approach to implementing the singleton pattern in a typescript ( version 2.1.6 ) class is as follows: export class NotificationsViewModel { private _myService: NotificationService; private _myArray: []; private static _instance: Notificatio ...

Merge rxjs streams, identify modifications, and yield a single result

In the context of using Angular with .net Core WebApi, let's consider the development of a time management application designed to monitor task durations. The user initiates a task on the front end which triggers a timer displaying elapsed time. The ...

Can I access the component attributes in Vuetify using Typescript?

For example, within a v-data-table component, the headers object contains a specific structure in the API: https://i.stack.imgur.com/4m8WA.png Is there a way to access this headers type in Typescript for reusability? Or do I have to define my own interfac ...

Tips for organizing your Typescript code in Visual Studio Code: avoid breaking parameters onto a

Currently, I am working on an Angular project using Visual Studio Code and encountering an irritating issue with the format document settings for Typescript files. It keeps breaking parameters to a new line: Here is an example of the code before formattin ...

What steps should I take to create a React component in Typescript that mimics the functionality of a traditional "if" statement?

I created a basic <If /> function component with React: import React, { ReactElement } from "react"; interface Props { condition: boolean; comment?: any; } export function If(props: React.PropsWithChildren<Props>): ReactElement | nul ...

How can parameters be implemented in Angular similar to NodeJs Express style?

Is there a way to implement a Parameter in Angular routes that resembles the NodeJs style? I have a route like **http://myhost.domain.com/signin**" and I want to pass an id for the signin request. Although I can achieve this using **http://myhost.doma ...

Utilizing string to access property

Is there a better way to access interface/class properties using strings? Consider the following interface: interface Type { nestedProperty: { a: number b: number } } I want to set nested properties using array iteration: let myType: Type = ...

Utilizing Typescript for Axios Response

Incorporating Typescript into my project, I encountered a tedious issue while making an API call using axios. The problem lies within handling nested data properly. Despite my belief that I have correctly typed everything, I persistently face a Typescript ...

Unable to position text in the upper left corner for input field with specified height

In a project I'm working on, I encountered an issue with the InputBase component of Material UI when used for textboxes on iPads. The keyboard opens with dictation enabled, which the client requested to be removed. In attempting to replace the textbox ...

What could be causing my sinon test to time out instead of throwing an error?

Here is the code snippet being tested: function timeout(): Promise<NodeJS.Timeout> { return new Promise(resolve => setTimeout(resolve, 0)); } async function router(publish: Publish): Promise<void> => { await timeout(); publish(&ap ...

Ionic: Automatically empty input field upon page rendering

I have an input field on my HTML page below: <ion-input type="text" (input)="getid($event.target.value)" autofocus="true" id="get_ticket_id"></ion-input> I would like this input field to be cleared eve ...

My instance transforms with the arrival of a JSON file

I'm grappling with a query about data types in TypeScript and Angular 2. I defined a class in TypeScript export class product{ public id:number; public name:string; public status:boolean; constructor(){} } and I initialize an instanc ...

Ways to keep information hidden from users until they actively search for it

Currently, I have a custom filter search box that is functioning correctly. However, I want to modify it so that the data is hidden from the user until they perform a search. Can you provide any suggestions on how to achieve this? Below is the code I am u ...

When trying to access the DOM from another module in nwjs, it appears to be empty

When working with modules in my nwjs application that utilize document, it appears that they are unable to access the DOM of the main page correctly. Below is a simple test demonstrating this issue. The following files are involved: package.json ... "ma ...

What is the recommended data type for the component prop of a Vuelidate field?

I'm currently working on a view that requires validation for certain fields. My main challenge is figuring out how to pass a prop to an InputValidationWrapper Component using something like v$.[keyField], but I'm unsure about the type to set for ...

Bidirectional enumeration in TypeScript

I am working with an enum defined as: enum MyEnum { key1 = 'val1' key2 = 'val2' } However, I am unsure how to create a SomeType implementation that fulfills the following requirements: Function: const myFunction = (param: SomeT ...

tips for preventing issues when the data array is empty

Here is the JSON data that I am dealing with: { "id": 43, "dataEvento": "2022-09-01T00:00:00.000+0000", "dataInvio": null, "idComunicazioneAssociata": null, "certificatoMedico" ...

What is the process for importing types from the `material-ui` library?

I am currently developing a react application using material-ui with typescript. I'm on the lookout for all the type definitions for the material component. Despite attempting to install @types/material-ui, I haven't had much success. Take a look ...

What is the best way to utilize the Moment.js TypeScript definition file in a website that already has moment.min.js integrated?

Currently, I am in the process of transitioning a website to utilize TypeScript by converting one JavaScript file at a time. All pages on my site are already linked to moment.js, such as: <script src="/scripts/moment.min.js"></script> I have ...