fp-ts、sequenceT 和甜蜜的异步类型 FP

2025-06-05

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.mapT.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
PREV
使用 JavaScript 实现《黑客帝国》效果 CMatrix - JavaScript 中的矩阵效果
NEXT
MLH 奖学金:入会和体验