Unable to disclose the discriminated union type and generic types due to limitations

Currently working with TypeScript version 5.4.5, I've developed the following utility helpers:

type Result<T, E> = [T, null] | [null, E];

function ok<T>(good: T): Result<T, null> {
    return [good, null];
}

function err<E>(bad: E): Result<null, E> {
    return [null, bad];
}

I have a utility function that utilizes the Result type for fetch responses which compiles without any TypeScript errors:

type FetchResult = Result<Response, Error>;
async function tryFetch(fetch: () => Promise<Response>): Promise<FetchResult> {
    try {
        const result = await fetch();
        if (!result.ok) {
            return [null, new Error(`Failed to fetch: ${result.statusText}`)];
        }
        return [result, null];
    } catch (error) {
        return [null, error as Error];
    }
}

Now, presenting another helper function that aims to follow the same type system but encounters a TypeScript error:

type FetchJsonResult<T> = Result<T, Error>;
async function tryFetchJson<T>(fetchFn: () => Promise<Response>): Promise<FetchJsonResult<T>> {
    try {
        const response = await fetchFn();
        if (!response.ok) {
            return err(new Error(`Failed to fetch: ${response.statusText}`));
        }
        const data: T = await response.json();
        return ok(data);
    } catch (error) {
        return err(error as Error);
    }
}

The encountered errors are:

Type 'Result<null, Error>' is not assignable to type 'FetchJsonResult<T>'.
  Type '[null, null]' is not assignable to type 'FetchJsonResult<T>'.
    Type '[null, null]' is not assignable to type '[T, null]'.
      Type at position 0 in source is not compatible with type at position 0 in target.
        Type 'null' is not assignable to type 'T'.
          'T' could be instantiated with an arbitrary type that may not relate to 'null'.

and

Type 'Result<T, null>' is not assignable to type 'FetchJsonResult<T>'.
  Type '[null, null]' is not assignable to type 'FetchJsonResult<T>'.

and

Type 'Result<null, Error>' is not assignable to type 'FetchJsonResult<T>'.

Is there a more efficient way to achieve the above using a single implementation of ok and err without requiring a cast to FetchJsonResult?

Answer №1

Is there a reason not to simplify and allow TypeScript to infer the return type?:

Code Playground

function ok<T extends object>(good: T): [T, null] {
return [good, null];
}

function err<E extends Error>(bad: E): [null, E] {
return [null, bad];
}

async function tryFetchJson<T extends object>(fetchFn: () => Promise<Response>) {
try {
const response = await fetchFn();
if (!response.ok) {
return err(new Error(`Failed to fetch: ${response.statusText}`));
}
const data: T = await response.json();
return ok(data);
} catch (error) {
return err(error as Error);
}
}

const r = tryFetchJson(() => fetch('domain') ); // const r: Promise<[null, Error] | [object, null]>

The usage of as const would be beneficial:

function ok<T extends object>(good: T) {
return [good, null] as const;
}

function err<E extends Error>(bad: E) {
return [null, bad] as const;
}

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

Creating a custom enum using generics in TypeScript can be excessively intricate

Is there a simpler way to streamline this code? It feels very repetitive and not quite right... const FolderVisibility = new Enum<{ PUBLIC: 'public', PRIVATE: 'private' }>({ PUBLIC: 'public', PRIVATE: &a ...

Issues detected with the functionality of Angular HttpInterceptor in conjunction with forkJoin

I have a Service that retrieves a token using Observable and an HttpInterceptor to inject the token into every http request. It works seamlessly with a single request, but when using forkJoin, no response is received. Here is the code for the interceptor: ...

Tips for enhancing loader methods for optimal use with loading indicator components in Vue

To display loading progress, I've implemented a component with a slot and an enum prop like the following example: <template> <div class="loaded-listing"> <div class="loaded-listing__loading-placeholder" v-if="isLoading"> ...

Issue with binding background images to DIV elements in Angular 4 CSS

Here is a template example: <div [style.background-image]="profileImage" ></div> In the TypeScript file: We declare private profileImage: any; and use DomSanitizer for security. Fetching photo from service: We set this.profileImage using b ...

Tips for concealing tick labels in d3 using TypeScript

When trying to hide tick labels by passing an empty string to the .tickFormat("") method, I encountered an issue with Typescript. The error message received was as follows: TS2769: No overload matches this call. Overload 1 of 3, '(format: null): Axi ...

Restrictions on classes in Typescript using generics

I'm currently working on a straightforward Local Storage utility method. My goal is to retrieve an entry based on a key and a specified type. Here's the implementation: get<T>(key: string): T { const data = localStorage.getItem(key); ...

Verify the data types of components received as props in a Typescript React application

I have a question regarding type checking in React components passed as props: What is the method for ensuring that only allowed components are passed as props? Allow me to demonstrate. We have the component we wish to pass around: type CustomProps = { ...

Encountered the error message 'The function getStaticPaths is not defined' while working with Next.js

As a newcomer to Nextjs, I am currently working on a blog project utilizing the T3 Stack (Nextjs, Typescript, Prisma, tRPC). I have encountered an error while attempting to fetch post content by ID. Below are the relevant sections of my code. Any assistanc ...

How come the information I receive when I subscribe always seems to mysteriously disappear afterwards?

I've been working on a web project using Angular, and I've run into an issue with my code that's been causing problems for a while now. The problem lies in fetching data from a server that contains translations: getTranslations(): Observab ...

Encapsulating constructor variables in TypeScript classes through private access modifiers and using public getters

Coming from a background in C#, I am used to designing most of my classes to be immutable. I am curious about whether it is considered good practice in TypeScript to use private constructor variables and public getters for accessing data within classes. T ...

Tips for preserving shopping cart in Angular?

As I delve into Angular, my goal is to create a straightforward ecommerce platform that allows users to add items to their cart, view them, and complete a checkout process. To accomplish this, I have set up three components: the products list, cart, and c ...

"What is the best way to calculate the total value of an array in TypeScript, taking into account the property

I'm currently working on a small Angular project that involves managing an array of receipt items such as Coke, Fanta, Pepsi, Juice, etc. Each receipt item has its own price and quantity listed. receiptItems: Array<ReceiptItem>; Here is the st ...

Tips for extracting the value of T from a Promise<T>

I have a scenario where an async function is declared with a return type as follows: async function getNumber() { const {number} = await API_getNumber(); return number; } export type Return = ReturnType<typeof getNumber> In this case, Return ...

Implement handleTextChange into React Native Elements custom search bar component

I need help with passing the handleTextChange function in the SearchBarCustom component. When I try to remove onChangeText={setValue} and add onchange={handleTextChange}, I am unable to type anything in the search bar. How can I successfully pass in the ...

When restarting the React application, CSS styles disappear from the page

While developing my React application, I encountered a problem with the CSS styling of the Select component from Material UI. Specifically, when I attempt to remove padding from the Select component, the padding is successfully removed. However, upon refre ...

Guide on setting default key/value state in TypeScript React application

Having the task of converting a React app to Typescript, I'm struggling to properly set the initial state of a hash object. Here is the original javascript code: export default class Wizard extends PureComponent { constructor(props) { su ...

Using Knex.js for Dataloader

The code worked perfectly fine until I made the update to dataloader: 2.0.0 const productLoader = new DataLoader(async keys => { const products: Product[] = await knex('product') .whereIn('id', keys) .select() const prod ...

Aurelia: The passing down of views and view-models

In the process of developing an Aurelia app, I am tasked with creating functionality that allows users to display various lists for different resources. These lists share common features such as a toolbar with search and refresh capabilities, along with a ...

Describing a function in Typescript that takes an array of functions as input, and outputs an array containing the return types of each function

Can the code snippet below be accurately typed? function determineElementTypes(...array: Array<(() => string) | (() => number) | (() => {prop: string}) | (() => number[])>) { /// .. do something /// .. and then return an array ...

How to show table cell value in Angular 4 using condition-based logic

I am a beginner in Angular development. Here is the HTML code I am working with: <tr *ngFor="let item of calendarTableSelected; let idx = index"> <span *ngIf="idx === 0"> <td style="width:15%;" *ngFor="let name of item.results" ...