TaskEither-1

192 阅读2分钟

什么时候使用TaskEither

当发起一个异步请求,可能会报错,也可能会返回数据的时候,此时就可以使用TaskEitherPromise包装起来。

案例1

发起一个异步请求,获取到年龄,年龄小于18岁,返回error,否则返回年龄。

一般写法

首先写一个检查年龄的函数:

const checkAgeAsync = (age: number) => {
    if(age >= 18){
        return Promise.resolve("Allowed.");
    }
    return Promise.reject(new Error("Not allowed."));
}

通过.then.catch方式获取结果:

checkAgeAsync(20)
    .then(mag => console.log(msg))
    .catch(error => console.log(error)) // Allowed.
    
checkAgeAsync(12)
    .then(mag => console.log(msg))
    .catch(error => console.log(error)) // Not allowed

通过async/await方式获取结果:

const main = async () => {
    try {
        // 返回年龄Allowed.
        const allowedAge = await checkAgeAsync(20);
        // 返回Not allowed
        const underAge = await checkAgeAsync(12);
    } catch (error) {
        console.log({ error });
    }
}

main();

使用TaskEither方式

通过TaskEither改造检查函数

import * as TE from "fp-ts/TaskEither";

// 返回 <B extends number>(b: B) => TE.TaskEither<Error, B>
const checkAgeTaskEither = TE.fromPredicate(
    (age: number) => age >= 18,
    () => new Error("Not allowed.")
);

使用TaskEither改造main函数

const mainWithTaskEither = async () => {
    try {
        // checkAgeUsingTaskEither(20) 返回 TE.TaskEither<Error, 20>
        // TE.TaskEither继承TE.Task<Either<E, A>>
        // interface Task<A> { (): Promise<A> }
        // checkAgeUsingTaskEither(20)() 返回 Promise<Either<Error, number>>
        const allowedAge = await checkAgeUsingTaskEither(20)(); // Either<Error, 20>
        
        const result = pipe(
            allowedAge, // Either<Error, 20>
            E.matchW(
                error => `${error}`,
                msg => msg
            )
        );
        
        console.log(result);
    } catch (error) {
        
    }
}

mainWithTaskEither();

案例2

通过网络请求一个第三方api,然后使用结果去请求另一个服务。

一般写法

请求第三方api

import axios from "axios";

const fetchAPI = async (url: string) => {
    try {
        const response = await axios.get(url);
        return response;
    } catch (error) {
        throw new Error(String(error));
    }
};

模拟数据库客户端类型

type DbClientMock = {
    findById: (id: number) => Promise<{
        id: number,
        name: string
    }>
};

使用id查询数据库

const fetchDB = (dbClient: DbClientMock) => async (id: number) => {
    try {  
        const response = await dbClient.findById(id);  
        return response;  
    } catch (error) {  
        throw new Error(String(error));  
    }
};

主函数

const main = async () => {  
    try {  
        const apiUrl = 'https://api.github.com/users/github';  
        const apiResult = await fetchAPI(apiUrl);  
        const id = apiResult.data.id;  
  
        // 模拟数据库客户端 
        const dbClientMock = {  
            findById: (id: number) => Promise.resolve(({ id, name: "demoObjName" }))  
        };  
   
        const dbResult = await fetchDB(dbClientMock)(id);  
        console.log(dbResult)  
    } catch (error) {  
        console.error(error);  
    }  
}  

main()

TaskEither方式

请求第三方api

import * as TE from "fp-ts/TaskEither";
import axios from "axios";

const fetchAPIUsingTaskEither = TE.tryCatch(
    (url: string) => axios.get(url),
    reason => new Error(String(reason))
);

模拟数据库客户端类型

type DbClientMock = {
    findById: (id: number) => Promise<{
        id: number,
        name: string
    }>
};

使用id查询数据库

import * as TE from "fp-ts/TaskEither";

const fetchDbUsingTaskEither = (dbClient: DbClientMock) => TE.tryCatchK(
    (id: Parameters<DbClientMock['findById']>[0]) => dbClient.findById(id),  
    (reason) => new Error(String(reason))
);

主函数

import * as E from "fp-ts/Either";
import * as TE from "fp-ts/TaskEither";
import { flow } from "fp-ts/lib/function";

const fetchDbUsingTaskEither = async () => {
    try {
        const apiUrl = 'https://api.github.com/users/github';
        
        const dbClientMock = {
            findById: (id: number) => Promise.resolve(({ id, name: "demoObjName" }))
        };
        
        const resultInTaskEither = pipe(
            apiUrl,
            fetchApiUsingTaskEither,
            TE.chain((apiResult) => fetchDbUsingTaskEither(dbClientMock)(apiResult.data.id))
        );
        
        const resultInEither = await resultInTaskEither();
        pipe(
            resultInEither,
            E.match(
                error => throw error,
                result => console.log(result)
            )
        )
    } catch (error) {
        console.error(error);
    }
};

mainWithTaskEither()