-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Flow error handling and launchIn #1263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
|
Question: If the order cannot be enforced, I'm a little worried about coding mistakes that are easy to make but hard to figure out. |
Agree with @streetsofboston, I think this inconsistency could potentially be confusing:
I would rather keep |
Oops. I've corrected the text. It was supposed to be |
I don't see any easy way to enforce an order and I don't see why it needs to enforced. It's like
(If go with this naming, we need to change the names of all the other Note, that you can use "error catching" operators multiple times to catch errors in differents parts of the flow:
|
Maybe I'm missing something, but why are we trying to simulate try/catch instead of using it? Ex: your ui code could be uiScope.launch {
try {
responseDataFlow().collect { updateDisplay(it) }
} catch (e: Exception) {
showErrorMessage(e)
} finally {
enableActionButtons()
}
} I mean, maybe the proposed operators would be useful in other situations, but for the ui use-case this seems way more clean too me. |
Yes. The goal is have more concise and more composable way to do Generally, using "bare" |
Hi, Better way would be just transfer monadic So if: val Try.value get() = when (it) { is Success -> it.value, else -> throw (it.e) }
// Used imaginary futures kotlin's keyword `try` which is expression with return `Try<T>` object
try {
responseDataFlow()
.map { try { first(it) } }
.map { when (it) {
is Success -> try { second(it.value) }
is Failure -> // <-- handle failure
}
}
.collect { it ->
println(it.value)
}
}
} catch (e) { globalHandler(e) } B. and if there is a need global handling (use example by @evant ): uiScope.launch {
try {
responseDataFlow().collect { updateDisplay(it) }
} catch (e: Exception) {
showErrorMessage(e)
} finally {
enableActionButtons()
}
} |
Are there other alternatives for this error handling proposal? It feels that this contradicts a coroutine's design which solved Future/Promise like API for error handling and now we can use just try/catch/finally instead. Exception is imperative construct which bubbles up the call stack until caught so why do we need "declarative way" of handling it? Maybe better we should make harder use of Exceptions inside a flow stream instead of adding API's which are hard to reason. |
@antanas-arvasevicius I'm not sure I understand your concerns. This new error handling approach is not a replacement, but an addition. Please, check out topic-started issue. I think it gives a pretty completing use-case for this new mechanism. |
@elizarov What is my concern that this addition will become "best practice" and we end up in the Promise API hell as was before coroutines implemented, and seems like Flow is becoming just implementation of ReactiveStreams specification. From practice I can say these operators which a planned in this topic is overused and it make harm to codebases. Programmers when see these API they tend to be cool and do everything with cool stream operators all system over time becomes stream oriented with stream DSL with no "legacy sh*t". Better that Flow would be just a kind of async iterator with With this goal as you said - |
@antanas-arvasevicius I frankly believe that in flow-UI interaction the main problem is not the Flow, but the highly-verbose nature of state-based UI frameworks. If you combine flow with any kind of reactive framework where you don't have to "manually" worry about synchronization of your state to UI, then it is much more natural fit. As for replacement of flow with some language mechanism in the same way that suspending functions had replaced flow, this is indeed one possible direction of future progress. |
Plus I don't see how these proposed APIs are more composable than plain if someone needs flow operator which handles errors globally there is no need extra fun Flow.withErrorHandler() = flow {
uiScope.launch {
try {
collect { emit(it) }
} catch (e: Exception) {
showErrorMessage(e)
} finally {
enableActionButtons()
}
}
} also responseDataFlow()
.onEach { updateDisplay(it) }
.launchIn(uiScope) When and why this should be used instead of just Flow / Streams in general is not a simple task to understand for a new programmer and by adding multiple ways to accomplish the same thing in the library makes more difficult to learn new concepts. Ps. when we're thinking in stream as a kind of streams of transformations (filtering, mapping) then APIs as I understand that for current Android Java-ish developers this is cool and nice and it's similar as RxJava, but please review this design and look for more opinions which are not affected by RxJava / Android users, at least defer it for later. Pps. these are not stream operators they are not working on items on stream, but just are "terminating" callbacks. And stream (Flow) is designed for sequences of items not for a single item. |
@antanas-arvasevicius Using In IMHO, Oh and I you want to handle errors from a |
@antanas-arvasevicius One can indeed write their own
This implementation is clear, concise, and a correct one. |
These two are completely equivalent. You should use the one which better fits your coding style. If most of your code is written in a declarative way with flow operators, then you'd naturally prefer the former, but if most of your code is imperative without much use of operators, then you'd naturally prefer the latter. |
Thanks @elizarov for explanation of Exception transparency. To be clear how catch behaves? It will create new flow which collects a previous flow stream and when exception is got it calls a lambda and terminates downstream flow? As I understand every flow cannot proceed further if exception is got/thrown? Same as in RxJava Rx.Net? It just ends/completes with an exception? Can catch re-throw or bypass exception if it's not interesting to similar as keyword Is there are special exceptions similar to coroutines cancellation exceptions? |
@antanas-arvasevicius You can take a look at the source: |
Flow solves a very specific problem: working with asynchronous streams of data. Treating everything as such streams is one way to write a reactive application, but it's not the only way. I think it's become the popular thing to do for Android because RxJava was much nicer to work with and compose than a bunch of simple callback APIs, but in a lot of cases simple coroutine features are nicer still. As @LouisCAD said, you can still use all the other, simpler coroutine APIs when the functionality they provide is all you need. However, there are times when you actually have a stream of data, and in those cases you want something as expressive and powerful as Flow or Reactive Streams. Every app is different, choose an architecture that makes sense for your app. |
~~Is it possible that errors that happens in child jobs while
Pointelss stuff
|
@lamba92 You should create the |
The I was putting the Sorry to have bothered for such a trivial oversight. |
Uh oh!
There was an error while loading. Please reload this page.
Here is a proposal to address the pattern of code that is commonly found in UI code that needs to launch a coroutine collecting a flow and updating UI such that:
launchIn
The first piece is
launchIn(scope)
operator that launches a coroutine collecting a flow and returns aJob
. Hereby I propose that this operator does not take any lambda, so that the replacement for the original code pattern is:This usage pattern maintains a consistent rule that execution context specifications in flows always work on "upstream" flows (like in
flowOn
). Another reason for such a syntactic form will be apparent in the next sections.onError
The most-generic basic handling operator is
onError
. It catches exception that happens in flow before this operator is applied and pass the caught exception to the supplied lambda. Example:Notice how
onError
is written afteronEach
to resemble the regulartry/catch
code. Here it is important thatcollectIn
takes no lambda and cannot fail, so writingonError
beforelaunchIn
always catches all exceptions.Implementation note:
onError
operator is already implemented in flow code, but now it is a private function calledcollectSafely
that is used internally to implement other error-handling operators.onCompletion
This operator calls its lambda whenever a flow completes for any reason, essentially working as
try/finally
:Advanced error handling
In addition to passing in an exception,
onError
operator also hasFlowCollector
receiver, which enables concise encoding of common error-handling patterns to replace an error with a value emitted to the flow. Some of those patterns are already provided as ready-to-use operators:TBD: Shall we rename them to
onErrorEmitAll
andonErrorEmit
or leave them as is?But
onError
can be used directly for more advanced case. For example, if there is a flow of someResponse
objects that support aResponse.Failure
case that wraps exception, then one can easily translate an exception in the upstream stream to a failed response:Retry
For consistency I also propose to rename
retry
toonErrorRetry
.Open questions
Shall
onError
be configured with(Throwable)->Boolean
predicate that defaults to "always true" just like the otheronErrorXxx
operators? It is not ideal to have a two-lambda function, but predicate here is a "strategy" that might be stored in a separate variable, which actually might result in quite a readable code likeonError(applicationErrors) { e -> displayAppErrorMessage(e) }
onError
vsonCompletion
. One might envision a separate set ofonCompletionXxx
operators that are similar toonErrorXxx
but also perform the corresponding action on the normal completion of the flow, optionally accepting a(Throwable?)->Boolean
predicate. It is an open question if there are any use-cases to that.The text was updated successfully, but these errors were encountered: