Either a b and Future a b are both capable of expressing failure/success. When working with asynchronous computations it is usually better to use Future a b rather than Future a (Either b c). The simpler, flatter type requires less mapping: S.map (f) rather than S.map (S.map (f)). Another advantage is that the error value is always in the same place, whereas with Future a (Either b c) both a and b represent failed computations.
We may, though, already have a validation function that returns an either. For example:
// validateEmail :: String -> Either String String
const validateEmail = s =>
s.includes ('@') ? S.Right (S.trim (s)) : S.Left ('Invalid email address');
If we have a value fut of type Future String String, how do we validate the email address fut may contain? The first thing to try is always S.map:
S.map (validateEmail) (fut) :: Future String (Either String String)
It would be nice to avoid this nesting. In order to do so, we first need to define a function from Either a b to Future a b:
// eitherToFuture :: Either a b -> Future a b
const eitherToFuture = S.either (Future.reject) (Future.resolve);
We can now transform an either-returning function into a future-returning function:
S.compose (eitherToFuture) (validateEmail) :: String -> Future String String
Let's revisit our use of S.map:
S.map (S.compose (eitherToFuture) (validateEmail)) (fut) :: Future String (Future String String)
We still have nesting, but now the inner and outer types are both Future String _. This means we can replace S.map with S.chain to avoid introducing nesting:
S.chain (S.compose (eitherToFuture) (validateEmail)) (fut) :: Future String String