emmanueltouzery / prelude-ts

Functional programming, immutable collections and FP constructs for typescript and javascript
ISC License
377 stars 21 forks source link

Question: Using Future with Either #7

Closed RPallas92 closed 6 years ago

RPallas92 commented 6 years ago

I have an example where I need to fetch some data asynchronously and then validate it, which I think lends itself to a type of Future

What do you think to the idea of a new type that consolidates Future with Either?

For example, the Left/Failure of my Future could be a network error.

Without a single type to represent this pattern, I have to map over the Future and then map over the Either, which is quite verbose and I don't really need that level of control.

emmanueltouzery commented 6 years ago

So you're saying... Why not having Future<L,R>? Fail the Future and put your error info in there. And as far as I can tell, it's true that we can put any value in a rejected promise, not only strings or primitive types. In effect now in prelude we have Future<any,R>. You can put anything in the error info, and recover it. But it's not documented through the type.

But adding that second type parameter brings a certain number of things... Let's say we'd want Future<MyBusinessError,MyBusinessSuccess>. But in reality you can only get Future<MyBusinessError|Error,MyBusinessSuccess>, because in case of network error for instance, you will (as far as I know) get an Error in the failure.

The mozilla documentation says "For debugging purposes and selective error catching, it is useful to make reason an instance of Error.". However as with most things in JS, it's not mandatory. Ultimately, most of the time, we build Futures from Promises and there is no guarantee on what will be their left type. In practice it could be anything, the types don't tell us. So most of the time people would have to use Promise<any,T>.

I thought about it some time when coming up with prelude's Future, and in the end what convinced me to go this way was looking at the alternatives.. In scala and java... No library offers a left type. java's Future, scala's Future, scalaz Future, scalaz/monix Task... In scala/java, the left type must inherit from Throwable, and so their reasoning must have been "i can't guarantee what people can put on that left side at runtime... You should check.. All i can tell you is that it's throwable" (except in JS we can't even say that). I'm only guessing though.

So currently my thought is... If I say the left type is any I'm not lying, for sure. In the end, if you want to be safe, you should pattern match on the error value to check what it is.

So, if you have an error type specific to your application... you could write a type guard:

function isMyBusinessError(error: any): error is MyBusinessError {
  return error.field1 !== undefined && error.field2 !== undefined;
}

Then you could fail the future the way you want to, and down the road use Future.mapFailure or some other method, which gives you a any and use the type guard to find out whether the error is your internal type, or something else (external error, like a network error). The something else is 99% Error but... Not 100% I guess.

What do you think? As an aside while experimenting on this question I found a bug in Future.mapFailure, I'll publish a fix on npm in the coming hours.

emmanueltouzery commented 6 years ago

(I released 0.7.8 with the Future.mapFailure fix)

emmanueltouzery commented 6 years ago

Maybe Future.filter can be useful to you. If you have a successful future (call to the server succeeded), but in practice it's a failure (the response body describes an error), you can use filter to fail the future. Down the line you can use Future.onComplete and inspect the left to differenciate on the type of error.

RPallas92 commented 6 years ago

Thanks @emmanueltouzery I think you are right, The Future.mapFailure and Future.filter are enough, and no Either is needed is this case.