Search code examples
javascripttypescriptsupabase

TS - Handle `{data, error} | {data, error}[]` and throw if `error` exists


I'm using supabase. In supabase, API response is structured like {data, error}. data is non-null if the request is succeeded and error is non-null if the request is failed. So If I want to call an api, I have to handle the response like this:

const {data, error} = await getData();

if (error) {
    throw error;
}

// data is non-null (succeeded)

It could be very burdened if the response is an array:

const [{data: d1, error: e1}, {data: d2, error:e2}] = await Promise.all([getData1(), getData2()]);


if (e1 || e2) {
  throw e1 || e2;
}

// d1 and d2 are non-null

So I've created an util function to reduce the effort.

import {type PostgrestError} from "@supabase/supabase-js";

interface IResponse<T> {
    data: T;
    error: PostgrestError | null;
}

interface ISuccessResponse<T> extends IResponse<T> {
    data: NonNullable<T>;
    error: null;
}

const isDataResponse = <T>(res: IResponse<T>): res is ISuccessResponse<T> => !res.error;

export const throwIfError = <T>(res: IResponse<T>): ISuccessResponse<T> => {
    if (isDataResponse(res)) {
        return res;
    }

    throw res.error;
};
// very simple!
const {data} = throwIfError(await getData());

// data is non-null!

It works well if the response is not an array. However it cannot handle array response.

const [{data: d1}, {data: d2}] = throwIfError(await Promise.all([getData1(), getData2()]));

I surely know how to write a logic for JS but not TS.

export const throwIfError = res => {
    if (Array.isArray(res)) {
        if (res.every(isDataResponse)) {
            return res;
        }

        throw res.find(r => !isDataResponse(r)).error;
    }

    if (isDataResponse(res)) {
        return res;
    }

    throw res.error;
}

I want throwIfError to handle array response as well. Could you help me?


Solution

  • I noticed that the throwIfError doesn`t have array response defination. So the solution can be below:

    Use overloads to provide the response types. Checking all the response if the response type is an array.

    interface IResponse<T> {
        data: T;
        error: Error | null;
    }
    
    interface ISuccessResponse<T> extends IResponse<T> {
        data: NonNullable<T>;
        error: null;
    }
    
    const isDataResponse = <T, >(res: IResponse<T>): res is ISuccessResponse<T> => !res.error;
    
    export function throwIfError<T>(res: IResponse<T>): ISuccessResponse<T>;
    export function throwIfError<T extends unknown[]>(res: { [K in keyof T]: IResponse<T[K]> }): { [K in keyof T]: ISuccessResponse<T[K]> };
    export function throwIfError<T>(res: IResponse<T> | IResponse<unknown>[]): ISuccessResponse<T> | ISuccessResponse<unknown>[] {
        if (Array.isArray(res)) {
            const errorResponse = res.find(r => !isDataResponse(r));
            if (errorResponse) {
                throw errorResponse.error;
            }
            return res as ISuccessResponse<unknown>[];
        }
        
        if (isDataResponse(res)) {
            return res;
        }
    
        throw res.error;
    }
    
    

    Usage:

    declare function getData1(): Promise<IResponse<{a: string} | undefined>>
    declare function getData2(): Promise<IResponse<{b: number} | undefined>>
    
    async () => {
        const [{data: d1}, {data: d2}] = throwIfError(await Promise.all([getData1(), getData2()])); // d1 has to be {a: string} and d2 has to be {b: number}
        const { data: d3 } = throwIfError(await getData1())
    }
    

    Hope this coule help you.