fp-ts、sequenceT 和甜蜜的异步类型 FP
最近我发现需要进行一组具有不同返回类型的异步调用。这是一个相当常见的任务,我们希望并行执行一些调用,并在所有调用完成后收集结果。让我们看一下fp-ts 异步任务的文档。
const tasks = [task.of(1), task.of(2)]
array
.sequence(task)(tasks)()
.then(console.log) // [ 1, 2 ]
嗯。这很好,但是当类型不同时会发生什么?
const tasks = [T.task.of(1), T.task.of("hello")]
array
.sequence(task)(tasks)()
.then(console.log) // [1, "hello"] I hope?
真是的。 的类型sequence
是 (simplified) Array[F[A]] => F[Array[A]]
,所以所有返回类型都必须相同。
怎么办? :/
经过一番谷歌搜索后,我偶然发现了神奇的序列T。
/**
* const sequenceTOption = sequenceT(option)
* assert.deepStrictEqual(sequenceTOption(some(1)), some([1]))
* assert.deepStrictEqual(sequenceTOption(some(1), some('2')), some([1, '2']))
* assert.deepStrictEqual(sequenceTOption(some(1), some('2'), none), none)
*/
太棒了!好的,我们试试吧。
import * as T from 'fp-ts/lib/Task'
import { sequenceT } from 'fp-ts/lib/Apply'
import { pipe } from 'fp-ts/lib/pipeable'
pipe(
sequenceT(T.task)(T.of(42), T.of("tim")), //[F[A], F[B]] => F[A, B]
T.map(([answer, name]) => console.log(`Hello ${name}! The answer you're looking for is ${answer}`))
)();
Hello tim! The answer you're looking for is 42
太棒了。pipe
它允许我们将调用串联起来,所以 的结果sequenceT
会传递给T.map
。T.map
解构元组,这样我们就可以对数据进行一些保证,从而随心所欲地处理。但是,如果我们的任务失败了怎么办?
pipe(
sequenceT(TE.taskEither)(TE.left("no bad"), TE.right("tim")),
TE.map(([answer, name]) => console.log(`Hello ${name}! The answer you're looking for is ${answer}`)),
TE.mapLeft(console.error)
)();
no bad
太棒了!好了,是时候开始尝试一下了。如果我们真的要调用某个 API,并且希望确保从 API 获得的结果符合预期的模式,该怎么办?
让我们通过使用一个方便的 http 客户端来访问虚拟 REST 端点来尝试一下axios
。
import { array } from 'fp-ts/lib/Array'
import axios, { AxiosResponse } from 'axios';
import * as t from 'io-ts'
//create a schema to load our user data into
const users = t.type({
data: t.array(t.type({
first_name: t.string
}))
});
//schema to hold the deepest of answers
const answer = t.type({
ans: t.number
});
//Convert our api call to a TaskEither
const httpGet = (url:string) => TE.tryCatch<Error, AxiosResponse>(
() => axios.get(url),
reason => new Error(String(reason))
)
/**
* Make our api call, pull out the data section and decode it
* We need to massage the Error type, since `decode` returns a list of `ValidationError`s
* We should probably use `reporter` to make this nicely readable down the line
*/
const getUser = pipe(
httpGet('https://reqres.in/api/users?page=1'),
TE.map(x => x.data),
TE.chain((str) => pipe(
users.decode(str),
E.mapLeft(err => new Error(String(err))),
TE.fromEither)
)
);
const getAnswer = pipe(
TE.right(42),
TE.chain(ans => pipe(
answer.decode({ans}),
E.mapLeft(err => new Error(String(err))),
TE.fromEither)
)
)
/**
* Make our calls, and iterate over the data we get back
*/
pipe(
sequenceT(TE.taskEither)(getAnswer, getUser),
TE.map(([answer, users]) => array.map(users.data, (user) => console.log(`Hello ${user.first_name}! The answer you're looking for is ${answer.ans}`))),
TE.mapLeft(console.error)
)();
Hello George! The answer you're looking for is 42
Hello Janet! The answer you're looking for is 42
Hello Emma! The answer you're looking for is 42
Hello Eve! The answer you're looking for is 42
Hello Charles! The answer you're looking for is 42
Hello Tracey! The answer you're looking for is 42
太棒了!我们做到了!异步类型函数式编程,人人适用!:)
文章来源:https://dev.to/gnomff_65/fp-ts-sequencet-and-sweet-sweet-async-typed-fp-5aop