Reactive Programming in Mobile Applications — a Voyage

The ‘Promise’ Model, in Kotlin — Part 2 — Error Handling with the ‘Catch’

Guy Michael
Feb 11 · 9 min read

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’.

Introduction
In the first chapter, we covered the basics for creating a Promise model in Kotlin, which allows for JavaScript-like Promise usage, but with extra features, such as thread-handling and Multiplatform capabilities.
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.

Find the error, find Waldo

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:

A multi-promise chain with multiple catch usages

Now let’s consider the possible ‘catch’ flows:

  1. 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 thenAwait wouldn’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.
  2. 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 T to 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 :

The simplified Promise class declaration, this time with error handlers.

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 this.single.map (through 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 then.

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 catch).
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 Promise .
It just returns this (Promise).
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 single.subscribe().
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:

We’ll call 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.

Back to 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:

When singleOfAsync’s 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 flatMap computes.
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 execute).

Note: now the name asyncErrorConsumer makes sense — it’s the error handler that handles the error chain of an async task, meaning, another Promise .

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 onErrorReturn method).

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.

‘catchReturn’

This clause helps recover from errors by providing an alternative T, much like a ‘then’:

‘catchResume’

This clause helps recover from errors by providing an alternative Promise<T>, much like a ‘thenAwait’:

‘catchIgnore’

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 Unit.
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.

‘thenMapOrCancel’, ‘thenMapOrReject’

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:

‘finally’

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:

Summary

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!

Nerd For Tech

From Confusion to Clarification

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To stay up to date on other topics, follow us on LinkedIn. https://www.linkedin.com/company/nerdfortech

Guy Michael

Written by

Developer and UX consultant; Code Architect; specialized in Android & Web (React.js) applications. An advocate of Kotlin Multiplatform and MVI.

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To stay up to date on other topics, follow us on LinkedIn. https://www.linkedin.com/company/nerdfortech

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store