- commit
- c1d8bf0
- parent
- 7a40ffe
- author
- Eric Bower
- date
- 2024-08-26 03:35:20 +0000 UTC
docs: copy
4 files changed,
+96,
-16
+5,
-0
1@@ -100,6 +100,11 @@ func main() {
2 Href: "/loaders",
3 Page: pager("loaders.md"),
4 },
5+ {
6+ Text: "Error Handling",
7+ Href: "/error-handling",
8+ Page: pager("error-handling.md"),
9+ },
10 {
11 Text: "Structured Concurrency",
12 Href: "/structured-concurrency",
+43,
-0
1@@ -0,0 +1,43 @@
2+---
3+title: Error handling
4+description: How to manage errors
5+---
6+
7+By leveraging [structured concurrency](/structured-concurrency) we can let it do
8+most of the heavy lifting for managing errors.
9+
10+There are some tools `starfx` provides to make it a little easier.
11+
12+By default in `effection`, if a child task raises an exception, it will bubble
13+up the ancestry and eventually try to kill the root task. Within `starfx`, we
14+prevent that from happening with [supervisor](/supervisors) tasks. Having said
15+that, child tasks can also control how children tasks are managed. Sometimes you
16+want to kill the child task tree, sometimes you want to recover and restart, and
17+sometimes you want to bubble the error up the task ancestry.
18+
19+If you want to capture a task and prevent it from bubbling an exception up, then
20+you have two `fx`: `call` and `safe`.
21+
22+```ts
23+import { call, run, safe } from "starfx";
24+
25+function* main() {
26+ try {
27+ // use `call` to enable JS try/catch
28+ yield* call(fetch("api.com"));
29+ } catch (err) {
30+ console.error(err);
31+ }
32+
33+ // -or- if you don't want to use try/catch
34+ const result = yield* safe(fetch("api.com"));
35+ if (!result.ok) {
36+ console.error(result.err);
37+ }
38+}
39+
40+await run(main);
41+```
42+
43+Both functions will catch the child task and prevent it from bubbling up the
44+error.
+47,
-15
1@@ -3,10 +3,9 @@ title: Supervisors
2 description: Learn how supervisor tasks work
3 ---
4
5-A supervisor task is a way to monitor children tasks and probably most
6-importantly, manage their health. By structuring your side-effects and business
7-logic around supervisor tasks, we gain very interesting coding paradigms that
8-result in easier to read and manage code.
9+A supervisor task is a way to monitor children tasks and manage their health. By
10+structuring your side-effects and business logic around supervisor tasks, we
11+gain interesting coding paradigms that result in easier to read and manage code.
12
13 [Supplemental reading from erlang](https://www.erlang.org/doc/design_principles/des_princ)
14
15@@ -27,19 +26,20 @@ function* supervisor() {
16 }
17 ```
18
19-Here we call some task that should always be in a running and healthy state. If
20-it raises an exception, we log it and try to run the task again.
21+Here we `call` some task that should always be in a running and healthy state.
22+If it raises an exception, we log it and try to run the task again.
23
24 Building on top of that simple supervisor, we can have tasks that always listen
25 for events and if they fail, restart them.
26
27 ```ts
28-import { parallel, run, takeEvery } from "starfx";
29+import { parallel, run, take } from "starfx";
30
31 function* watchFetch() {
32- yield* takeEvery("FETCH_USERS", function* (action) {
33+ while (true) {
34+ const action = yield* take("FETCH_USERS");
35 console.log(action);
36- });
37+ }
38 }
39
40 function* send() {
41@@ -53,11 +53,43 @@ await run(
42 );
43 ```
44
45-Here we create a supervisor function using a helper `takeEvery` to call a
46-function for every `FETCH_USERS` event emitted.
47+Here we create a supervisor function using a helper `take` to call a function
48+for every `FETCH_USERS` event emitted.
49+
50+While inside a `while` loop, you get full access to its powerful flow control.
51+Another example, let's say we we only want to respond to a login action when the
52+user isn't logged in and conversely only listen to a logout action when the user
53+is logged in:
54+
55+```ts
56+function*() {
57+ while (true) {
58+ const login = yield* take("LOGIN");
59+ // e.g. fetch token with creds inside `login.payload`
60+ const logout = yield* take("LOGOUT");
61+ // e.g. destroy token from `logout.payload`
62+ }
63+}
64+```
65+
66+Interesting, we've essentially created a finite state machine within a
67+while-loop!
68+
69+We also built a helper that will abstract the while loop if you don't need it:
70+
71+```ts
72+import { takeEvery } from "starfx";
73+
74+function* watchFetch() {
75+ yield* takeEvery("FETCH_USERS", function* (action) {
76+ console.log(action);
77+ });
78+}
79+```
80
81 However, this means that we are going to make the same request 3 times, we
82-probably want a throttle or debounce to prevent this behavior.
83+probably want a throttle or debounce so we only make a fetch request once within
84+some interval.
85
86 ```ts
87 import { takeLeading } from "starfx";
88@@ -71,9 +103,9 @@ function* watchFetch() {
89
90 That's better, now only one task can be alive at one time.
91
92-Both thunks and endpoints simply listen for particular actions being emitted
93-onto the `ActionContext` -- which is just an event emitter -- and then call the
94-middleware stack with that action.
95+Both thunks and endpoints simply listen for
96+[actions](/thunks#anatomy-of-an-action) being emitted onto a channel -- which is
97+just an event emitter -- and then call the middleware stack with that action.
98
99 Both thunks and endpoints support overriding the default `takeEvery` supervisor
100 for either our officially supported supervisors `takeLatest` and `takeLeading`,
+1,
-1
1@@ -97,7 +97,7 @@ middleware stack will stop after the code execution leaves the scope of the
2 current middleware. This provides the end-user with "exit early" functionality
3 for even more control.
4
5-# Thunk action
6+# Anatomy of an Action
7
8 When creating a thunk, the return value is just an action creator:
9