Search code examples
typescriptfp-ts

If error happens throw error else return the value


Try to send an HTTP request with parameter validation.

If the parameter validation fails, throw "parameter invalid" error and stop the following executions.

If the code of the API response doesn't equal '0', throw res.message error else return the res.

I want to use try...catch... statements in the main function to catch any error mentioned above. And use the correct res.result in the try block.

import * as t from 'io-ts';
import * as T from 'fp-ts/lib/Task';
import * as TE from 'fp-ts/lib/TaskEither';
import * as E from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/function';
import { failure } from 'io-ts/lib/PathReporter';

export interface ApiResponse<Result = any> {
    code: string;
    message: string | null;
    result: Result;
}

const GetArticlesByPageRequestDTOCodec = t.type({
    currentPage: t.number,
    pageSize: t.number,
})

type GetArticlesByPageRequestDTO = t.TypeOf<typeof GetArticlesByPageRequestDTOCodec>;

type GetArticlesByPageQueryPayload = {
    pagination: any;
};

const getArticlesByPage = async (data: GetArticlesByPageRequestDTO) => {
    return {
        code: '0',
        message: '',
        result: {
            resultList: [
                { id: 1, title: 'test' },
                { id: 2, title: 'test1' },
            ],
            totalItem: 100,
        },
    };
};

const getArticlesByPageTaskEither = (data: GetArticlesByPageRequestDTO) =>
    TE.tryCatch<Error, ApiResponse>(
        () =>
            getArticlesByPage(data).then((res) => {
                if (res.code !== '0') {
                    throw new Error(res.message);
                }
                return res;
            }),
        (reason) => new Error(String(reason)),
    );

const getArticlesByPageService = ({ pagination }: GetArticlesByPageQueryPayload) => {
    const reqDTO: GetArticlesByPageRequestDTO = {
        pageSize: pagination.pageSize,
        currentPage: pagination.current,
    };
    return pipe(
        reqDTO,
        GetArticlesByPageRequestDTOCodec.decode,
        E.mapLeft((errors) => {
            console.error(
                `Validation failed for input: ${JSON.stringify(reqDTO, null, 2)}. Error details: ${failure(errors).join('\n')}`,
            );
            return new Error('parameter invalid');
        }),
        TE.fromEither,
        TE.chain(getArticlesByPageTaskEither),
    )();
};

async function main() {
    try {
        const res = await getArticlesByPageService({ pagination: { current: 1, pageSize: 10 } });
        console.log(res.result)
    } catch (e) {
        console.error(e)
    }
}

But the type of res is E.Either<Error, ApiResponse<any>>, so how to convert it to the Promise<ApiResponse<any>> type.

When I try to use res.result, got an error:

Property 'result' does not exist on type 'Either<Error, ApiResponse<any>>'.
  Property 'result' does not exist on type 'Left<Error>'

TypeScript Playground


Solution

  • There are multiple options. Firstly, you can just match on the Either in your main function

    async function main() {
      pipe(
        await getArticlesByPageService({
          pagination: { current: 1, pageSize: 10 },
        }),
        E.match(
          (e) => console.error(e),
          (res) => console.log(res.result)
        )
      );
    }
    

    Another option is to modify the getArticlesByPageService function to return TE.TaskEither<Error, ApiResponse<any>> instead of a promise (by simply removing the function evaluation at the very end of the function) and do something like this.

    async function main() {
      await pipe(
        getArticlesByPageService({
          pagination: { current: 1, pageSize: 10 },
        }),
        TE.orElseFirst((e) => TE.fromIO(Console.error(e))),
        TE.chainFirstIOK((res) => Console.log(res.result))
      )();
    }
    

    It is a good idea to perform side-effect only in IO or Task containers. Therefore, I'd personally prefere the that one.