Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ const posts = result.data
posts.map((post) => console.log(post.title));
```

### `.map`

```ts
const get = makeMightFail(axios.get);
const [ error, result ] = await get("/posts").map(post => post.title);

if (error) {
// handle error
return;
}

const titles = result.data
titles.map((post) => console.log(title));
```

## Sync


Expand Down
4 changes: 4 additions & 0 deletions docs/content/contents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ course:
filePath: /getting-started/makeMightFailSync.mdx
slug: /make-might-fail-sync
type: documentation
- title: "`.map` Transformations"
filePath: /getting-started/map-transformations.mdx
slug: /map-transformations
type: documentation
- title: "Either Type"
filePath: /getting-started/either.mdx
slug: /either
Expand Down
31 changes: 30 additions & 1 deletion docs/content/getting-started/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

* [❌ try, catch, finally](/try-catch-finally-is-bad)
* [`mightFail`](/might-fail)
* [`makeMightFail`](/make-might-fail)
* [`mightFailSync`](/might-fail-sync)
* [`makeMightFailSync`](/make-might-fail-sync)
* [`.map` Transformations](/map-transformations)
* [Static Methods](/static-methods)
* [Either Type](/either)
* [Might & Fail](/might-and-fail)
Expand Down Expand Up @@ -41,6 +44,32 @@ posts.map((post) => console.log(post.title));

The success case is now the only code that is **not** nested in another block. It's also at the very bottom of the function making it easy to find.

## Transform Results with `.map`

You can transform successful results without repeatedly handling errors:

```ts
// Transform results in a functional style
const [ error, postTitles ] = await mightFail(fetch("/posts"))
.map(response => response.json())
.map(posts => posts.filter(post => post.published))
.map(published => published.map(post => post.title))

if (error) {
// Handle any error from the entire chain
return
}

// postTitles contains the transformed result
console.log(postTitles) // Array of published post titles
```

The `.map` method:
- Only executes if the previous step succeeded
- Catches transformation errors automatically
- Can be chained for complex transformations
- Works with all might-fail functions

<Important>
The sucess case is always at the bottom of the function. All of your error handling logic is next to where the error might occur.
The success case is always at the bottom of the function. All of your error handling logic is next to where the error might occur.
</Important>
82 changes: 82 additions & 0 deletions docs/content/getting-started/makeMightFail.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,86 @@ posts.map((post) => console.log(post.title));
</Tab>
</Tabs>

## Transforming Results with `.map`

You can transform results directly on the returned promise:

<Tabs>
<Tab name="tuple">
```ts
const get = makeMightFail(axios.get);
const [error, titles] = await get("/posts")
.map(response => response.data)
.map(posts => posts.map(post => post.title))

if (error) {
// handle error
return;
}

console.log(titles); // Array of post titles
```
</Tab>
<Tab name="object">
```ts
const get = makeMightFail(axios.get);
const { error, result: titles } = await get("/posts")
.map(response => response.data)
.map(posts => posts.map(post => post.title))

if (error) {
// handle error
return;
}

console.log(titles); // Array of post titles
```
</Tab>
<Tab name="go">
```ts
const get = makeMightFail(axios.get);
const [titles, error] = await get("/posts")
.map(response => response.data)
.map(posts => posts.map(post => post.title))

if (error) {
// handle error
return;
}

console.log(titles); // Array of post titles
```
</Tab>
</Tabs>

This is particularly useful for API responses where you want to extract and transform specific data:

```ts
const fetchUser = makeMightFail(async (id: number) => {
const response = await fetch(`/users/${id}`)
return response.json()
})

const [error, userSummary] = await fetchUser(123)
.map(user => ({
name: user.name,
email: user.email,
isActive: user.lastSeen > Date.now() - 86400000 // 24h
}))
.map(summary => ({
...summary,
displayName: summary.isActive ? `${summary.name} (Online)` : summary.name
}))

if (error) {
console.error('Failed to fetch user:', error.message)
return
}

console.log(userSummary)
```

<Note>
Learn more about transformations in the [`.map` Transformations](/map-transformations) guide.
</Note>

210 changes: 210 additions & 0 deletions docs/content/getting-started/map-transformations.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# `.map` Transformations

The `.map` method allows you to transform successful results without having to handle errors repeatedly. It provides a functional programming style for chaining transformations while preserving error handling.

## Basic Usage

Instead of awaiting, checking for errors, and then transforming:

```ts
// Without .map - more verbose
const [error1, userData] = await mightFail(fetchUser(123))
if (error1) return

const [error2, userName] = await mightFailSync(() => userData.name.toUpperCase())
if (error2) return

console.log(userName)
```

You can chain transformations directly:

```ts
// With .map - concise and readable
const [error, userName] = await mightFail(fetchUser(123))
.map(user => user.name.toUpperCase())

if (error) return

console.log(userName)
```

## Available on All Functions

The `.map` method is available on all might-fail functions:

<Tabs>
<Tab name="mightFail">
```ts
const [error, result] = await mightFail(promise)
.map(data => data.title.toUpperCase())

if (error) {
console.error('Error:', error.message)
return
}

console.log(result) // Transformed data
```
</Tab>

<Tab name="makeMightFail">
```ts
const safeGetUser = makeMightFail(getUserById)

const [error, result] = await safeGetUser(123)
.map(user => user.name)

if (error) {
console.error('Error:', error.message)
return
}

console.log(result) // User name
```
</Tab>

<Tab name="Static Methods">
```ts
// mightFail.all
const [error, result] = await mightFail.all([
fetchProject(id),
fetchProjectPositions(id)
]).map(([project, positions]) => ({
...project,
positionCount: positions.length
}))

// mightFail.race
const [error, winner] = await mightFail.race([
fetchFromServer1(),
fetchFromServer2()
]).map(data => data.result)

// mightFail.any
const [error, success] = await mightFail.any([
tryMethod1(),
tryMethod2(),
tryMethod3()
]).map(result => result.value)
```
</Tab>
</Tabs>

## Chaining Multiple Transformations

You can chain multiple `.map` calls for complex transformations:

```ts
const [error, summary] = await mightFail(fetchUserPosts(userId))
.map(posts => posts.filter(post => post.published))
.map(published => published.map(post => post.title))
.map(titles => titles.join(', '))
.map(titleList => `Published posts: ${titleList}`)

if (error) {
console.error('Failed to get post summary:', error.message)
return
}

console.log(summary) // "Published posts: Post 1, Post 2, Post 3"
```

## Error Handling

### Preserves Original Errors
If the original promise rejects, `.map` preserves the error:

```ts
const [error, result] = await mightFail(Promise.reject(new Error("API failed")))
.map(data => data.toUpperCase())

console.log(error?.message) // "API failed"
console.log(result) // undefined
```

### Catches Transformation Errors
If the transformation function throws, `.map` catches it:

```ts
const [error, result] = await mightFail(Promise.resolve(null))
.map(data => data.name.toUpperCase()) // Will throw on null

console.log(error?.message) // "Cannot read properties of null"
console.log(result) // undefined
```

### Error Short-Circuiting
Once an error occurs, subsequent `.map` calls are skipped:

```ts
const [error, result] = await mightFail(Promise.reject(new Error("API failed")))
.map(data => data.step1()) // Skipped
.map(data => data.step2()) // Skipped
.map(data => data.step3()) // Skipped

console.log(error?.message) // "API failed"
```

## Real-World Example

Here's a practical example combining API calls and transformations:

```ts
async function getUserDashboard(userId: string) {
const [error, dashboard] = await mightFail.all([
fetchUser(userId),
fetchUserPosts(userId),
fetchUserStats(userId)
]).map(([user, posts, stats]) => ({
user: {
name: user.name,
email: user.email
},
summary: {
totalPosts: posts.length,
publishedPosts: posts.filter(p => p.published).length,
totalViews: stats.views,
lastActive: user.lastSeen
}
})).map(data => ({
...data,
summary: {
...data.summary,
description: `${data.user.name} has ${data.summary.publishedPosts} published posts with ${data.summary.totalViews} total views`
}
}))

if (error) {
console.error('Failed to load dashboard:', error.message)
return null
}

return dashboard
}
```

## Type Safety

The `.map` method maintains full type safety. TypeScript will infer the correct types through the transformation chain:

```ts
// TypeScript knows the types at each step
const [error, result] = await mightFail(Promise.resolve({ count: 5 }))
.map(data => data.count) // number
.map(count => count * 2) // number
.map(doubled => doubled.toString()) // string

// result is inferred as string | undefined
// error is inferred as Error | undefined
```

## Destructuring Still Works

The transformed result maintains the iterable `Either` interface:

```ts
// Both work identically
const { error, result } = await mightFail(promise).map(transform)
const [error, result] = await mightFail(promise).map(transform)
```
Loading