You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# maybe-result - Safe function return handling without null and undefined in Typescript and Javascript
1
+
# maybe-result - Safe function return handling in Typescript and Javascript
2
2
3
-
## Introduction
3
+
Deciding when a function should return `undefined`, throw an `Error`, or return some other indicator that
4
+
"something isn't right" is tough. We don't always know how users _calling_ our function
5
+
will use it, and we want to be clear, clean, and safe.
4
6
5
-
In many languages, we have concepts of exceptions but also a `null` value of some sort.
7
+
This library provides two approaches for wrapping function results:
8
+
9
+
-[Maybe](#maybe) for when something may or may not exist
10
+
-[Result](#result) for when you might have an error, but want to let the caller decide how to handle it
11
+
12
+
## Maybe
13
+
14
+
In many languages, we have concepts of exceptions and `null` values.
6
15
(JavaScript has both `null` and `undefined`. Ugh!)
7
16
8
17
Often a function will need to indicate when a value _maybe_ exists, or it does not.
9
18
In JavaScript, the "does not" is usually returned as `undefined` or `null`, but sometimes
10
-
a function will *throw* an `Error` type instead. Thus, the developer needs to figure out
19
+
a function will _throw_ an `Error` type instead. Thus, the developer needs to figure out
11
20
how that particular function behaves and adapt to that if they want to handle
12
21
the missing value.
13
22
14
23
Finally, throwing Errors in TypeScript can be expensive, as a stack trace must be
15
24
generated and cross-referenced to the `.js.map` files. These stack traces to your
16
-
TypeScript source are immensely useful to trace actual errors, but are wasted
25
+
TypeScript source are immensely useful for tracing actual errors, but they are wasted
17
26
processing when ignored.
18
27
19
28
The `Maybe` type makes this cleaner. Elm was an early language that defined this.
@@ -26,16 +35,18 @@ the possibly missing value. The caller can explicitly check for `isValue` or
26
35
It is now the caller's choice. There are many other helper functions too, such as
27
36
to unwrap with a default value to return in place of throwing if `isNone`.
28
37
29
-
This is not an "anti-throw" utility like Rust's `Result` type is.
30
38
In JavaScript we like to throw `Error` types, but in other languages we call these _exceptions_.
31
39
**Throwing is still good for _exceptional_ cases. `Maybe` is for "normal" control flows.**
32
40
41
+
`Maybe` is not only for function return values. It may be used elsewhere where you want a type-safe
42
+
and immutable alternative to `undefined` and `null`.
43
+
33
44
Here's a nice introduction to the concept:
34
45
[Implementing a Maybe Pattern using a TypeScript Type Guard](https://medium.com/@sitapati/implementing-a-maybe-pattern-using-a-typescript-type-guard-81b55efc0af0)
35
46
36
-
## Example by story
47
+
###Example by Story
37
48
38
-
You might have defined a data repository class (access to a data store) like this:
49
+
You might define a data repository class (access to a data store) like this:
39
50
40
51
```ts
41
52
classWidgetRepository {
@@ -45,21 +56,22 @@ class WidgetRepository {
45
56
}
46
57
```
47
58
48
-
If the Widget isn't found, you throw a `NotFoundError`. All is well, until you start _expecting_
59
+
If the Widget isn't found, you throw a `NotFoundError`. All is well until you start _expecting_
49
60
a Widget not to be found. That becomes valid flow, so you find yourself writing this a lot:
50
61
51
62
```ts
52
-
let widget:Widget|undefined;
53
-
try {
54
-
widget=awaitrepo.get(widgetID);
63
+
let widget:Widget|undefined;
64
+
try {
65
+
widget=awaitwidgetRepo.get(widgetID);
66
+
} catch (error) {
67
+
if (!(errorinstanceofNotFoundError)) {
68
+
throwerror;
55
69
}
56
-
catch (error) {
57
-
if (!(errorinstanceofNotFoundError)) {
58
-
throwerror;
59
-
}
60
-
}
61
-
62
-
if (widget) { /* ... */ }
70
+
}
71
+
72
+
if (widget) {
73
+
/* ... */
74
+
}
63
75
```
64
76
65
77
You may be willing to do that once... but not more. So you first try to change the repository:
@@ -72,7 +84,7 @@ class WidgetRepository {
72
84
}
73
85
```
74
86
75
-
Now it returns `undefined` instead of throwing. Oh, but what a hassle now you have to _check_ for
87
+
Now it returns `undefined` instead of throwing. Oh, but what a hassle - now you have to _check_ for
76
88
`undefined`_every time_ you call the function! So instead, you define _two_ functions:
77
89
78
90
```ts
@@ -88,48 +100,119 @@ class WidgetRepository {
88
100
89
101
That makes it easier. It works. You just have to write _two_ functions every time you write a get function. 🙄
90
102
91
-
**OR...** use Maybe
103
+
**OR...** use `Maybe` 🎉
92
104
93
105
```ts
94
106
classWidgetRepository {
95
-
get(widgetID:string):PromiseMaybe<Widget> {
107
+
asyncget(widgetID:string):PromiseMaybe<Widget> {
96
108
// implementation ...
97
109
}
98
110
}
99
111
100
112
// One place elsewhere where you want to throw if not found
0 commit comments