Skip to content

Commit e8f5e30

Browse files
code
1 parent b758ccc commit e8f5e30

File tree

1 file changed

+74
-43
lines changed

1 file changed

+74
-43
lines changed

README.md

+74-43
Original file line numberDiff line numberDiff line change
@@ -53,80 +53,111 @@ Only the `catch (error) {}` block represents actual control flow, while no progr
5353

5454
<!-- Credits to https://x.com/LeaVerou/status/1819381809773216099 :) -->
5555

56-
The `try {}` block is often redundant, as its scoping lacks meaningful conceptual significance. It generally acts more as a code annotation than a genuine control flow construct. Unlike true control flow blocks, no program state exists that requires being confined to a `try {}` block.
56+
The `try {}` block often feels redundant because its scoping lacks meaningful conceptual significance. Rather than serving as an essential control flow construct, it mostly acts as a code annotation. Unlike loops or conditionals, a `try {}` block doesn’t encapsulate any distinct program state that requires isolation.
5757

58-
Conversely, the `catch {}` block **is** genuine control flow, making its scoping relevant and meaningful. According to Oxford Languages, an exception is defined as:
58+
On the other hand, the `catch {}` block **is** genuine control flow, making its scoping relevant. According to Oxford Languages, an exception is defined as:
5959

6060
> a person or thing that is excluded from a general statement or does not follow a rule.
6161
62-
Since `catch` handles exceptions, it is logical to encapsulate exception-handling logic in a block to exclude it from the general program flow.
62+
Since `catch` is explicitly handling exceptions, it makes sense to encapsulate exception-handling logic in a block that separates it from the normal program flow.
6363

64-
The pseudocode below illustrates the lack of value in nesting the success path within a code block:
64+
<br />
65+
66+
Consider a simple function like this:
6567

6668
```js
67-
async function handle(request, reply) {
68-
try {
69-
const userInfo = await cache.getUserInfo(request.id)
69+
function getPostInfo(session, postSlug, cache, db) {
70+
const user = cache.getUser(session.userId)
7071

71-
try {
72-
const posts = await db.getPosts(userInfo.authorId)
72+
const post = db.selectPost(postSlug, user)
73+
const comments = db.selectComments(post.id, user)
74+
75+
return { post, comments }
76+
}
77+
```
7378

74-
let comments
79+
<br />
80+
81+
But **production code is rarely this clean**. Error handling quickly forces a messier structure:
82+
83+
```js
84+
function getPostInfo(session, postSlug, cache, db) {
85+
let user
7586

76-
// Variables used after error handling must be declared outside the block
77-
try {
78-
comments = await db.getComments(posts.map((post) => post.id))
79-
} catch (error) {
80-
logger.error(error, "Posts without comments not implemented yet")
81-
return reply.status(500).send({ error: "Could not get comments" })
82-
}
87+
// Requires a dedicated error handler
88+
try {
89+
user = cache.getUser(session.userId)
90+
} catch (error) {
91+
otel.capture(error, Operations.GET_SELF)
92+
session.logout()
93+
throw new Error("Invalid session")
94+
}
8395

84-
// Do something with comments before returning
85-
return reply.send({ userInfo, posts, comments })
96+
// No recovery if selectPost fails
97+
try {
98+
const post = db.selectPost(postSlug, user)
99+
100+
let comments = []
101+
102+
// The post must still be returned even if fetching comments fails
103+
try {
104+
comments = db.selectComments(post.id, user)
86105
} catch (error) {
87-
logger.error(error, "Anonymous user behavior not implemented yet")
88-
return reply.status(500).send({ error: "Could not get posts" })
106+
otel.capture(error, Operations.JOIN_POST_COMMENTS)
89107
}
90108
} catch (error) {
91-
logger.error(error, "Maybe DB is down?")
92-
return reply.status(500).send({ error: "Could not get user info" })
109+
otel.capture(error, Operations.GET_POST)
110+
throw new Error("Could not get post")
93111
}
112+
113+
return { post, comments }
94114
}
95115
```
96116

97-
With the proposed `try` statement, the same function can be rewritten as:
117+
The `try` blocks didn't provide much value beyond introducing unnecessary nesting.
98118

99-
```js
100-
async function handle(request, reply) {
101-
const userInfo = try await cache.getUserInfo(request.id)
119+
Instead, using the proposed `try` operator simplifies the function:
102120

103-
if (!userInfo.ok) {
104-
logger.error(userInfo.error, "Maybe DB is down?")
105-
return reply.status(500).send({ error: "Could not get user info" })
121+
<br />
122+
123+
```js
124+
function getPostInfo(session, postId, cache, db) {
125+
const [userOk, userErr, user] = try cache.getUser(session.userId)
126+
127+
// Requires a dedicated error handler
128+
if (!userOk) {
129+
otel.capture(userErr, Operations.GET_SELF)
130+
session.logout()
131+
throw new Error("Invalid session")
106132
}
107133

108-
const posts = try await db.getPosts(userInfo.value.authorId)
134+
const [postOk, postErr, post] = try db.selectPost(postId, user)
109135

110-
if (!posts.ok) {
111-
logger.error(posts.error, "Anonymous user behavior not implemented yet")
112-
return reply.status(500).send({ error: "Could not get posts" })
136+
// No recovery if selectPost fails
137+
if (!postOk) {
138+
otel.capture(postErr, Operations.GET_POST)
139+
throw new Error("Could not get post")
113140
}
114141

115-
const comments = try await db.getComments(posts.value.map((post) => post.id))
142+
const [commentsOk, commentsErr, comments = []] = try db.selectComments(
143+
post.id,
144+
user
145+
)
116146

117-
if (!comments.ok) {
118-
logger.error(comments.error, "Posts without comments not implemented yet")
119-
return reply.status(500).send({ error: "Could not get comments" })
147+
// The post must still be returned even if fetching comments fails
148+
if (!commentsOk) {
149+
otel.capture(commentsErr, Operations.JOIN_POST_COMMENTS)
120150
}
121151

122-
// No need for reassignable variables or nested try/catch blocks
123-
124-
// Do something with comments before returning
125-
return reply.send({ userInfo: userInfo.value, posts: posts.value, comments: comments.value })
152+
return { post, comments }
126153
}
127154
```
128155

129-
A `try` statement provides significant flexibility and arguably results in more readable code. A `try` statement is a statement that can be used wherever a statement is expected, allowing for concise and readable error handling.
156+
This approach improves readability by cleanly separating the happy path from error handling.
157+
158+
Control flow remains linear, making it easier to follow, while only the "exceptions" in execution require explicit scoping.
159+
160+
The result is a more structured, maintainable function where failures are handled concisely without unnecessary indentation.
130161

131162
<br />
132163

0 commit comments

Comments
 (0)