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?
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.