If my single errors because of a networkexception return Single.just(false)
If my single errors because of another reason return Single.error
If my single succeeds return the original Single value.
this should be as easy as
getStudent(studentId)
.onErrorResumeNext { if (it is NetworkException) return #onErrorResumeNext Single.just(true)
return Single.error(it) }
Type inference failed. Expected type mismatch SingleSource found Single
Your Single needs to return the same type as your source (I'm assuming getStudent() isn't returning a Boolean). If you want to represent a "success" and "error" states, Kotlin has a Result class just for this.
E.g.
getStudent()
.map { student ->
// Your logic here may look different
Result.success(student)
}
.onErrorResumeNext { error ->
if (error is NetworkException){
Single.just(Result.failure(error))
} else {
Single.error(error)
}
}
This will catch network errors and wrap the exception in a Result, all other exceptions will be propagated downstream. You can then choose how to handle the error in your subscribe method.
Depending on your use case however, you may want to also look into using Maybe or the retry() operator.
Related
I have some code like this:
File("foo.txt").readAsString().catchError((e)=>print(e));
The compiler is complaining
info: The return type 'void' isn't assignable to 'FutureOr<T>', as required by 'Future.catchError'.
I can't seem to give it what it wants and can't find a single clear usage example in any of the docs (just a long issue in git about how many ways there are to mis-use this). If I take the docs at face value, I should be able to return a bool, or a future, neither make the analyzer happy.
How do I provide this FutureOr?
The documentation for Future.catchError could be a lot clearer, but the relevant part is:
onError is called with the error and possibly stack trace, and the returned future is completed with the result of this call in exactly the same way as for then's onError.
Cross-referencing to the documentation for Future.then, the relevant portion is:
The onError callback must return a value or future that can be used to complete the returned future, so it must be something assignable to FutureOr<R>.
Since File.readAsString returns a Future<String>, your catchError callback also must return a Future<String>. Examples of doing that:
File("foo.txt").readAsString().catchError((e) {
print(e);
return Future.value('');
});
File("foo.txt").readAsString().catchError((e) async {
print(e);
return '';
});
Logically, this makes sense; because given:
String value = await File("foo.txt").readAsString().catchError(...);
then if readAsString succeeds, value should be assigned a String. If it fails, since you catch the exception without rethrowing it, value still needs to be assigned a String.
Put another way, your code is equivalent to:
Future<String> readFoo() async {
try {
return await File("foo.txt").readAsString();
} catch (e) {
print(e);
}
// Oops, missing return value.
}
In general, I strongly recommend using async/await with try-catch instead of using .catchError, which would avoid this confusion.
I have some code like this:
File("foo.txt").readAsString().catchError((e)=>print(e));
The compiler is complaining
info: The return type 'void' isn't assignable to 'FutureOr<T>', as required by 'Future.catchError'.
I can't seem to give it what it wants and can't find a single clear usage example in any of the docs (just a long issue in git about how many ways there are to mis-use this). If I take the docs at face value, I should be able to return a bool, or a future, neither make the analyzer happy.
How do I provide this FutureOr?
The documentation for Future.catchError could be a lot clearer, but the relevant part is:
onError is called with the error and possibly stack trace, and the returned future is completed with the result of this call in exactly the same way as for then's onError.
Cross-referencing to the documentation for Future.then, the relevant portion is:
The onError callback must return a value or future that can be used to complete the returned future, so it must be something assignable to FutureOr<R>.
Since File.readAsString returns a Future<String>, your catchError callback also must return a Future<String>. Examples of doing that:
File("foo.txt").readAsString().catchError((e) {
print(e);
return Future.value('');
});
File("foo.txt").readAsString().catchError((e) async {
print(e);
return '';
});
Logically, this makes sense; because given:
String value = await File("foo.txt").readAsString().catchError(...);
then if readAsString succeeds, value should be assigned a String. If it fails, since you catch the exception without rethrowing it, value still needs to be assigned a String.
Put another way, your code is equivalent to:
Future<String> readFoo() async {
try {
return await File("foo.txt").readAsString();
} catch (e) {
print(e);
}
// Oops, missing return value.
}
In general, I strongly recommend using async/await with try-catch instead of using .catchError, which would avoid this confusion.
I know questions involving this error have been asked before, but I have looked through them and none of them have helped me solve this particular issue.
I have a List, which depending on a certain condition, will have some items filtered out. Here it is:
List(tasks) { task in
if (!self.toggleIsOn) || (!task.completed.status) {
TaskRowView(task)
}
}
Everything works fine, but when I add the conditional, it gives me this error:
Unable to infer complex closure return type; add explicit type to disambiguate
On the List line. How can I fix this?
List expects a View returned for every item, using Group like that is a bit of a hack.
Better to move the logic out of the List, either
var filteredTasks: [Task] {
return tasks.filter { !self.toggleIsOn || !$0.completed.status }
}
...
List(filteredTasks) { task in
TaskRowView(task)
}
or
List(tasks.filter( { !self.toggleIsOn || !$0.completed.status })) { task in
TaskRowView(task)
}
I managed to fix this problem by surrounding the returned value in a Group - this meant that the return type was always Group, even if the Group didn't contain anything.
List(tasks) { task in
Group {
if (!self.toggleIsOn) || (!task.completed.status) {
TaskRowView(task)
}
}
}
I have a GoLang API with an SPA to consume it. What I do to errors in my API is return them until the handler where I test if an error from previous functions exist. If there is an error, I put it inside the response body, set status code to either 400 or 500 then return the response
in the handler function, to be able to create a clear message to the client side, I need to know what kind of error was returned, how do I do it?
I know about error types but I read about Dave Cheney's recommendation to just return an error along with a message (or wrap them in other words).
But if there are so many kinds of errors which might occur in the API call, then does it mean before returning the response, I need to check them all just to know what message I should return?
The first thing to say about errors is that just because there's an error interface
type error interface {
Error() string
}
Does not mean that the error returned from any given method can only have that method / information attached to it.
One common method is to define your own error interface:
type myError interface {
error // embeds the standard error interface
OtherMethod() string // can define own methods here
}
When writing methods and functions it's really important to return an error and not myError, else you couple that method to your error implementation and cause dependency nightmares for yourself later.
Now that we've decided we can return extra information from error, using our own error interfaces you've got 3 main choices.
Sentinel errors
Error Failure types
Errors with Behaviour
Sentinel errors
Sentinel errors are error values that are defined as package level variables, are exported and allow comparison to check for errors.
package myPackage
var ErrConnectionFailed = errors.New("connection failed")
func Connect() error {
// trimmed ...
return ErrConnectionFailed
}
A consumer of this example could use the connect function:
if err := myPackage.Connect(); err == myPackage.ErrConnectionFailed {
// handle connection failed state
}
You can do a comparison to check if the error returned is equal to the sentinel error of the package. The drawback is that any error created with errors.New("connection failed") will be equal, and not just the error from myPackage.
Error failure types
Slightly better than sentinel errors are error failure types.
We've already seen that you can define your own error interface, and if we say ours is now:
type MyError interface {
error
Failure() string
}
type Err struct {
failure string
}
func (e *Err) Error() string {
// implement standard error
}
func (e *Err) Failure() string {
return e.failure
}
const ConnFailed = "connection failed"
err := &Err{failure: ConnFailed}
In the consumer code you can get an error, check if it implements MyError and then do things with it.
err := myPackage.Connect()
if myErr, ok := err.(myPackage.MyError); ok {
// here you know err is a MyError
if myErr.Failure() == myPackage.ConnFailed {
// handle connection failed, could also use a switch instead of if
}
}
Now you have an idea of what caused the error which is nice. But do you really care what the cause was? Or do you only really care what you want to do to handle that error.
This is where errors with behaviour are nice.
Errors with behaviour
This is similar to defining your own error type, but instead you define methods that report information about that error. Given the example above, do you really care that the connection failed, or do you really only care if you can retry or if you need to error up the call stack again?
package myPackage
// this interface could report if the error
// is temporary and if you could retry it
type tempErr interface {
Temporary() bool
}
func (e *Err) Temporary() bool {
// return if the error is temporary or not
}
Now in the consumer (note you don't need to use the myPackage.tempErr), you can test using type assertions if the error is temporary and handle the retry case:
err := myPackage.Connect()
if tmp, ok := err.(interface { Temporary() bool }); ok && tmp.Temporary() {
// the error is temporary and you can retry the connection
}
To answer the question, it's very hard to say without the specifics of the service that you are trying to implement. But as broad advice, I would try and use the last of the 3 examples as much as possible.
If the consumer of your service sends you some input that's not valid:
err := doThing(...)
if inv, ok := err.(interface { Invalid() bool }); ok && inv.Invalid() {
// input is invalid, return 400 bad request status code etc.
}
If you want to return a specific message to a consumer, you could make that a method of your error type. Warning: this would give your packages knowledge that they are being used in a web service, etc.
err := doThing(...)
if msg, ok := err.(interface { ResponseMsg() string }); ok {
// write the message to the http response
io.WriteString(response, msg.ResponseMsg())
}
TL;DR you would need to handle all the cases, but you can create error types that make the code much easier to work with!
If Single doesn't find the element you're expecting to exist then it throws an InvalidOperationException. Only trouble is that other things result in this exception too. For example an EF Code First model being out of date.
I've tried to narrow it down by checking the exception message. However this could change and I'd be none the wiser. Is there a better way of catching this problem?
try
{
return DbSet.Single(filter);
}
catch (InvalidOperationException exc)
{
if (exc.Message == "Sequence contains no elements")
{
throw new UserNotFoundException();
}
throw;
}
Use .FirstOrDefault() instead. Then check to see if the result is null. If it is, the user wasn't found.