If you're working with all things of the same applicative (and recall all monads are applicatives), the most straightforward way is:
type A = { /*...*/ }
type B = { /*...*/ }
declare const a: TaskEither<string, A>
declare const b: TaskEither<string, B>
sequenceS(TE.ApplyPar)({ a, b }) // TaskEither<string, { a: A, b: B }>; could also use TE.ApplySeq instead for sequential instead of parallel processing of async calls
the TaskEither
will produce left if either a
or b
is a left.
The sequence
variations (there are three, Apply.sequenceS, Apply.sequenceT, and Array.sequence) specifically exist to do what you are asking: take a record/array of monads and result in a record/array of their results if they all "succeed". Otherwise you get a single "failure."
declare const firstOpt: () => Option<string>
declare const secondOpt: () => Option<number>
sequenceT(Option.Apply)(firstOpt(), secondOpt())
// None or Option<[string, number]>
declare const firstEither: () => Left<string>
declare const secondEither: () => Right<number>
sequenceS(Either.Apply)({
first: firstEither(),
second: secondEither(),
})
// Left<string>
etc.