Reactive Programming in Mobile Applications — a Voyage
The ‘Promise’ Model, in Kotlin — Part 2 — Error Handling with the ‘Catch’
Handling errors properly and elegantly is a major advantage of the ‘Promise’ model. The ‘catch’ clause also allows for resuming the chain by providing a new value or even a new Promise to recover from an Exception.
This is part 2 of ‘The Promise Model’.
In this chapter we will cover error handling, namely, the ‘catch’ clause.
The next chapter-The Android APromise-will cover the APromise model, which is an extending class that supports Android platform specifically, by providing Android ‘Main Thread’ and View features.
The ‘catch’ clause, an introduction
The ‘Catch’ clause in the Promise model is basically an onError callback for the task-chain, except that it can also resume (back to
then) the chain to recover from Exceptions. You can use as many ‘catch’ as you want, in virtually any place in the chain, even inside inner chains (a
Promise returned inside a
then clause). The order in which they are called — in case an Exception is thrown — is crucial and expected to be in the same order as those
catch are written.
To understand my implementation of ‘catch’, let’s first consider this Promise chain, to understand how we’d expect our Promise to handle errors:
Now let’s consider the possible ‘catch’ flows:
- ofDelay(5000) fails:
We expect 1st-catch and 3rd-catch to fire (be called), in this order.
We do not expect 2nd-catch to be called, as
thenAwaitwouldn’t start if an Exception was thrown anywhere up the chain — and 2nd-catch belongs to the inner chain, not to the ‘original’, outer chain.
In fact, the whole
Promise.ofDelay(10000)chain can be written in a separate method — it has nothing to do with the outer, upstream (up the) chain; this is an important rule.
- ofDelay(10000) fails:
We expect 2nd-catch and 3rd-catch to fire, in this order.
This is because 2nd-catch belongs to the actual (inner) chain which raised the Exception and 3rd-catch is placed after (downstream of)
thenAwait— An inner chain which fails (throws an Exception) causes the whole chain to fail. This is also an important rule.
Note: the take here — besides the call-order of ‘catch’ clauses — is that although an inner chain has nothing to do with the (outer) upstream chain as seen in #1 (except for receiving its result through a ‘then’, of course), it does very much affect the (outer) downstream chain, as seen in #2 — it affects it both when an Exception is thrown (‘catch’ chain) and by mapping the result/result-type (‘then’ chain) — e.g. from
R, as seen in the previous chapter of this article.
The ‘catch’ implementation approach
Now instinctively, the easiest way to implement our ‘catch’ would be to make it use rx Single’s
doOnError — it’s a side-effect callback, which can be used many times in a chain, just like we’d like ‘catch’ to be.
Problem is, it may have unexpected call order, depending on the exact flow we would use. In the Promise-chain example above, for example, if ofDelay(10000) fails, 3rd-catch will be called before 2nd-catch(!).
I will not go into the rx details/reasons in this article, but the bottom line is:
we can’t count on
doOnError to behave exactly like we’d expect ‘catch’ to behave.
What we will do in order to overcome this, is to not use Single’s
doOnError. Instead, we will use our
Single’s subscription (inside the Promise’s
execute method, as seen in the previous chapter) to subscribe to errors and we will define our own error-handling-chain, which we will call from that subscription only. Don’t worry, everything will be explained.
Note: By the way, if anyone comes up with a more ‘Single-like’ approach, one that involves less ‘custom’ code, I will be more than happy to listen. The strength of this Promise implementation is the fact that it’s a
Single , and every ‘extra’ code makes it less so. That being said, this error handling worked flawlessly in 2 production apps, with some very complicated Promise chains. It works great.
Let’s start. Basically, we just need to keep in mind that we need multiple
catch (using the example above) to be called in the correct order.
We will do it by repeating the exact same steps we’ve covered until now, but with more code. The titles correspond to the steps in previous chapter.
Building a Promise, with Error Handling
We’ll start from the Promise class definition. Remember there were only 2 members, the
promiseSubscriber and the
single? So here’s another 2 :
As you can see, each Promise has an error handler for simple actions (such as the ‘then’) and one for asynchronous tasks (such as the ‘thenAwait’).
It’s hard to explain why at this stage, so we will explain that on the go.
Simply put though, it is done because the complexity of async operations requires a dedicated error handler, or the order of
catch calls will be disrupted (as seen in the
promise.ofDelay() example above).
Creating a new Promise instance, with Error Handling
Now, let’s return to the
createInstance method, this time with the error handling code:
Whenever we create a new
Promise instance (e.g. for a new ‘then’ action), we pass on the ‘current’ promise’s error handler to the new/next Promise. This is done because the last Promise in the chain is the only Promise that handles errors (and successes, actually). And this is because the last Promise's
Single is the only Single in the chain that we subscribe to. And a non-subscribed Single does not run, remember? The next step shows the updated
execute method, which will make it more clear.
Note: it is a good time to talk about the elephant in the room.
The last Promise is the only Promise that matters. A bit like that the last Single in an rx chain is the only Single that matters. All other Promises are there for ease of use (building the chain) and will be garbage collected. The last Promise holds the only subscribed-to
Single and therefore, holds the entire Single operations chain. If you’re sure you have a more efficient way, that does not compromise the reactive approach and the ease of use, please do let me know.
Executing the Promise, with Error Handling
Here is the new
execute method, with added error handling code:
As you can see, the Single’s
subscribe method takes two arguments, a success handler and an error handler. The success handler is of no importance as we use ‘then’ and ‘finally’ which affect the Single chain directly and immediately.
The error handler though, is where we actually handle errors — they are not handled inside the Single’s own chain like successes. Let that sink in for a moment, then continue reading.
Now, to make this point more clear, please return to the simple ‘then’ code in the first chapter. As you can see, it uses
singleOfConsumer()) which means it already affects the Single chain.
The ‘catch’ clause though, does not affect the Single chain. Keep reading.
The ‘Catch’ clause implementation
Here it is, at last, now that we understand more and improved previous code.
Take notice of how
catch does not affect the Single chain, as opposed to
It’s very simple, really — if there is no previous error handler, we just save the new consumer (the new error handler argument passed to
If there is a previous one (from previous
catch calls), create and replace it with a new error handler — which calls both the previous one and the new one, in the correct order. A bit similar to a linked list, in a general sense.
To return to the Single discussion — this
catch implementation does not affect the Promise’s
Single at all and therefore does not create a new
It just returns
The reason, if you remember, is related to ‘thenAwait’ —without handling inner Promise chains (like in the example above), we could’ve just used the Single’s
doOnError method. But with it, it would’ve disrupted the correct order of the ‘catch’ calls.
‘ThenAwait’, with Error Handling
Finally we got to the final part. The really tricky part. This ‘thenAwait’ is the whole reason that the ‘catches’ work differently than the ‘thens’; that ‘catches’ do no affect the Promise’s
Single and saved in an
onErrorConsumer instead; this is why we execute the error chain from inside the Promise’s
execute method, from within the
This is why we went through everything (since The ‘Catch’ clause, an introduction).
Let’s see the new
thenAwait and its
singleOfAsync, this time with proper error handling:
To understand what’s going on here, let’s remind ourselves of the example which started this whole section:
Promise.ofDelay(5000) — promise5 and
Promise.ofDelay(10000) — promise10. And our task is to make promise10’s 2nd-catch fire before 3rd-catch, when it (promise10) fails (rejects).
When this promise chain is created,
thenAwait runs on promise5 and calls
singleOfAsync(as seen in its implementation above).
Inside it, when
flatMap computes (and only then!), promise10’s (
nextPromise) error-handler is saved as promise5’s async-error-handler.
thenAwait. It calls promise5’s own
catch (let’s call it catch1.5) to handle any error before the 3rd catch, and this is the punch line:
flatMap computes, it creates promise10 (
function.apply(t)) and saves its error-handler as promise5’s async-error-handler.
And when promise10 fails, catch1.5 gets called, in which point promise5’s async-error-handler is not null and already holds promise10’s error-handler, which gets called (by catch1.5) before 3rd-catch. Order restored:)
Take notice that if some error occurred before
thenAsync (e.g. caught by 1st catch), promise10 does not yet exist(!), as it is a late-init Promise, which initializes only when
thenAwait starts, which is when the
Before that happens, promise5’s async-error-handler is still null.
Also, as promise10 is not the last Promise in the chain, it will not handle errors whatsoever (because our
catch implementation doesn’t affect the
single, the only place where we execute the error-handler is inside
Note: now the name
asyncErrorConsumer makes sense — it’s the error handler that handles the error chain of an async task, meaning, another
That’s it, really:)
Let’s view a few more ‘catch’ types usages, such as the ‘catchResume’. We will not get into details because they pretty much have the same implementation, or use a simple one (like ‘catchReturn’ which just uses the Single’s
More useful ‘catch’ types
Below are some more, interesting ‘catch’ clauses.
They also offer a way to recover from an Exception that would otherwise stop the chain from continuing.
This clause helps recover from errors by providing an alternative
T, much like a ‘then’:
This clause helps recover from errors by providing an alternative
Promise<T>, much like a ‘thenAwait’:
This one is less used but it’s still worth mentioning. It helps when you want to continue some chain, regardless of its success ‘so far’ — it always maps to a
Note: unless there are more related ‘then’ and ‘catch’ down the chain, it’s normally better to just use
finally to do actions when the Promise finishes, regardless of success/failure.
Before we finish, there are some more ‘then’ versions I’d like to present now. These versions can be easily made for ‘catch’ clauses too, but as I’ve never needed them, they’re not yet in the source code:)
The likes of ‘thenMapOrCancel’, ‘thenAwaitOrReject’ (etc.) help when you can’t know beforehand if you’ll be able to provide a value (or a Promise)— in which case you may return null to break the chain, either by cancelling or by rejecting:
And just because I can’t write an article about a Promise without showing this one (although already shown in some examples), here is ‘finally’, which is called always, regardless of cancelation, success or rejection:
So this concludes the Promise model in pure Kotlin. I really hope you enjoyed it and that it taught you something new. Please write your thoughts in the comments below.
The next chapter will focus on the APromise, which is an extending class that supports Android context (View, Activity). It is the equivalent of using the Kotlin Coroutines’s ‘Scope’ to auto-cancel a chain when some context is no longer relevant. Hopefully, future releases will support iOS as well (IPromise) — provided that the underlying Promise is written in pure Kotlin.
After this next chapter, we will conclude the Promise section of the reactive architecture and move much deeper, into the core library — Reactdroid.
As they say… This was just the beginning…
Have a good one;)
Edit: next part is out!