2

I want to make two parallel requests using sequenceT function and work with results, but it shows me an error which I cannot resolve on my own

import * as RTE from 'fp-ts/ReaderTaskEither'
import * as Ap from 'fp-ts/Apply'

Ap.sequenceT(RTE.ApplyPar)(
  getUserById('1'),
  getSpecialistById('2')
) // <- This method shows error

Error

Type 'SpecialistNotFoundError' is not assignable to type 'UserNotFoundError'.
          Types of property 'code' are incompatible.
            Type '"SPECIALIST_NOT_FOUND"' is not assignable to type '"USER_NOT_FOUND"'

Code that may help to understand the problem

function getSpecialistById (specialistId: string): RTE.ReaderTaskEither<PrismaClient, SpecialistNotFoundError, Specialist> {
  return (prisma: PrismaClient) => {
    return TE.tryCatch(
      () => prisma.specialist.findUnique({ where: { id: specialistId }, rejectOnNotFound: true }),
      () => new SpecialistNotFoundError()
    )
  }
}

function getUserById (userId: string): RTE.ReaderTaskEither<PrismaClient, UserNotFoundError, User> {
  return (prisma: PrismaClient) => {
    return TE.tryCatch(
      () => prisma.user.findUnique({ where: { id: userId }, rejectOnNotFound: true }),
      () => new UserNotFoundError()
    )
  }
}

class UserNotFoundError extends Error {
  readonly code = 'USER_NOT_FOUND'

  constructor () {
    super('User not found')
  }
}

class SpecialistNotFoundError extends Error {
  readonly code = 'SPECIALIST_NOT_FOUND'

  constructor () {
    super('Specialist not found')
  }
}
Roman Mahotskyi
  • 4,576
  • 5
  • 35
  • 68

1 Answers1

1

I think rather than using sequenceT (as I don't think it's capable of handling the types correctly for what you're trying to do) I would instead use Do notation like follows:

const result = pipe(
  RTE.Do,
  RTE.apS("user", getUserById("1")),
  RTE.apSW("spec", getSpecialistById("2")),
  RTE.map(({ user, spec }) => {
    return `${user.id}:${spec.id}`;
  })
);

result will be RTE.ReadTaskEither<PrismaClient, UserNotFoundError | SpecialistNotFoundError, string> in this case since I returned a string in map.

Here are the docs on Do notation. Note at the bottom where it shows how to do things in parallel

Souperman
  • 5,057
  • 1
  • 14
  • 39
  • Yes, it works! Btw, is there any rule of thumb when to use `Do` notation vs simple `pipe`/`flow`? Or `Do` notation is preferable approach of writing fp apps? – Roman Mahotskyi May 23 '22 at 10:15
  • I think `Do` notation is just sort of the newer way of writing stuff (it's a relatively new API). In my opinion it seems to be a bit friendlier to work with. I think that `pipe`/`flow` are going to be in your toolbox no matter what but `Do` notation is good for when you're working with multiple Monads at once and you want to combine them all together. It often comes up when you have multiple `Eithers` specifically because it can handle this case nicer than `Sequence` – Souperman May 23 '22 at 10:18
  • The problem with `sequenceT` in the snippet shared is that the error types of these `ReaderTaskEither` are different and `sequenceT` requires them to be the same. The problem with the `Do` notation and `apS` is that only a single error can be propagated even though multiple errors could have occurred. In https://stackoverflow.com/a/72600492/4874344 I shared an idea how we may use `sequenceT` to unwrap multiple results and multiple errors in a type-safe way. – Voreny Jun 13 '22 at 09:35