什么时候使用TaskEither
当发起一个异步请求,可能会报错,也可能会返回数据的时候,此时就可以使用TaskEither将Promise包装起来。
案例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()