Repository: jwstegemann/fritz2
Issue: Unclear error handling

**Is your feature request related to a problem? Please describe.**
I'm learning Fritz2, and I'm making a login form. When user enters bad credentials, and the API returns HTTP 401, I'd like to show the error (toast). I can't find a way how.

Another related issue is in `Tracker`. When I start the remote request, and it fails, the button keeps spinning and I couldn't find any method how to stop it.

**Describe the solution you'd like**
* Make special chapter in documentation about error handling.
* Demonstrate it in remotes example.
  * I have uBlock, which blocks the request to the domain you use in this example. I see the error in developer console, but nothing in the UI.

Abouty the tyracker problem, I'd suggest to change the `track` method like this:
```kotlin
// OLD (0.10.1)
return operation().also { state.value = null } 

// NEW
return try { operation() } finally { state.value = null }
```

**Describe alternatives you've considered**
I couldn't find any. Just provide some documentation and example.

**Additional context**
My handler looks like this. If you could show me how to display the error toast, I'd be grateful.

```kotlin
val login = handle { model ->
    loading.track {
        if (validator.isValid(model, Unit)) {
            try {
                http("/apiIs2/authUser")
                    .acceptJson()
                    .contentType("application/json")
                    .body(
                        JSON.stringify(
                            json(
                                "username" to model.login, "password" to model.password
                            )
                        )
                    )
                    .post()
                    .getBody()
                router.navTo("main")
            } catch (t: Throwable) {
                console.log(t)
                toast { // NOT DISPLAYED :(
                    content {
                        alert {
                            title("Login error")
                            content(
                                if (t is FetchException && t.statusCode == 401.toShort()) {
                                    "Bad credentials"
                                } else {
                                    "$t"
                                }
                            )
                        }
                    }
                }
            }
        }
        model
    }
}
```


Changed files:
core/src/jsTest/kotlin/dev/fritz2/tracking/tracking.kt
core/src/jsMain/kotlin/dev/fritz2/tracking/tracking.kt

diff --git a/core/src/jsMain/kotlin/dev/fritz2/tracking/tracking.kt b/core/src/jsMain/kotlin/dev/fritz2/tracking/tracking.kt
index 8b6a92b2..134e00ac 100644
--- a/core/src/jsMain/kotlin/dev/fritz2/tracking/tracking.kt
+++ b/core/src/jsMain/kotlin/dev/fritz2/tracking/tracking.kt
@@ -36,12 +36,19 @@ class Tracker(
     /**
      * Tracks a given [operation].
      *
+     * Works also with unsafe operations that throw exceptions, as the tracking gets stopped. The exceptions are
+     * not swallowed though.
+     *
      * @param transaction text describing the transaction
      * @param operation function to track
      */
     suspend fun <T> track(transaction: String = defaultTransaction, operation: suspend () -> T): T {
         state.value = transaction
-        return operation().also { state.value = null }
+        return try {
+            operation()
+        } finally {
+            state.value = null
+        }
     }
 
     /**
diff --git a/core/src/jsTest/kotlin/dev/fritz2/tracking/tracking.kt b/core/src/jsTest/kotlin/dev/fritz2/tracking/tracking.kt
index 45f699cb..562fb727 100644
--- a/core/src/jsTest/kotlin/dev/fritz2/tracking/tracking.kt
+++ b/core/src/jsTest/kotlin/dev/fritz2/tracking/tracking.kt
@@ -66,4 +66,51 @@ class TrackingTests {
         assertEquals(endValue, valueAfterTransaction)
 
     }
+
+    @Test
+    fun testStopTrackingIfExceptionOccursDuringOperation() = runTest {
+        initDocument()
+
+        val resultElementId = "tracker-${uniqueId()}"
+
+        val store = object : RootStore<Int>(0) {
+            val running = tracker()
+
+            val handler = handle {
+                try {
+                    running.track {
+                        delay(500)
+                        throw Exception("Something unexpected happened")
+                    }
+                } catch (ex: Exception) {
+                    // we just don't want to let this escape to the log...
+                }
+                it
+            }
+        }
+
+        render {
+            div {
+                span(id = resultElementId) { store.running.data.map { if (it) "running" else "stopped" }.asText() }
+            }
+        }
+
+        delay(100)
+
+        store.handler()
+
+        val valueBeforeTransaction = document.getElementById(resultElementId)?.textContent
+        assertEquals("", valueBeforeTransaction)
+
+        delay(200)
+
+        val valueDuringTransaction = document.getElementById(resultElementId)?.textContent
+        assertEquals("running", valueDuringTransaction)
+
+        delay(500)
+
+        val valueAfterTransaction = document.getElementById(resultElementId)?.textContent
+        assertEquals("stopped", valueAfterTransaction)
+
+    }
 }
\\ No newline at end of file