Skip to content

Commit 9cf1c54

Browse files
Add TaskResult.ofCatchTask (#353)
* Initial plan * Add TaskResult.ofCatchTask with doc comments, tests, and gitbook docs Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com> * Fix incorrect example code in doc comments and gitbook docs Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com> * Fix ofTask.md Example 2 to correctly show exception propagation behavior Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: TheAngryByrd <1490044+TheAngryByrd@users.noreply.github.com>
1 parent 1c6f444 commit 9cf1c54

5 files changed

Lines changed: 92 additions & 4 deletions

File tree

gitbook/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@
208208
* [sequenceTaskResultA](list/sequenceTaskResultA.md)
209209
* Transforms
210210
* [ofAsync](taskResult/ofAsync.md)
211+
* [ofCatchTask](taskResult/ofCatchTask.md)
211212
* [ofResult](taskResult/ofResult.md)
212213
* [ofTask](taskResult/ofTask.md)
213214

gitbook/taskResult/ofCatchTask.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# TaskResult.ofCatchTask
2+
3+
Namespace: `FsToolkit.ErrorHandling`
4+
5+
Transforms a `Task<'T>` into a `Task<Result<'T, exn>>`, catching any exceptions thrown by the task and wrapping them in the `Error` case.
6+
7+
Unlike [`ofTask`](ofTask.md), this function catches exceptions that escape from the task and maps them to `Error`. If the task completes successfully, the result is wrapped in `Ok`.
8+
9+
## Function Signature
10+
11+
```fsharp
12+
Task<'T> -> Task<Result<'T, exn>>
13+
```
14+
15+
## Examples
16+
17+
### Example 1
18+
19+
```fsharp
20+
let result = TaskResult.ofCatchTask (task { return 42 })
21+
// task { return Ok 42 }
22+
```
23+
24+
### Example 2
25+
26+
```fsharp
27+
let result = TaskResult.ofCatchTask (task { failwith "something went wrong" })
28+
// task { return Error (System.Exception("something went wrong")) }
29+
```

gitbook/taskResult/ofTask.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
Namespace: `FsToolkit.ErrorHandling`
44

5-
Transforms a `Task<'T>` into a `Task<Result<'T, exn>>`.
5+
Transforms a `Task<'T>` into a `Task<Result<'T, exn>>` by wrapping the value in `Ok`.
6+
7+
> **Note:** This function does **not** catch exceptions thrown by the task. Any exceptions will propagate as-is. To catch exceptions and map them to the `Error` case, use [`ofCatchTask`](ofCatchTask.md).
68
79
## Function Signature
810

@@ -22,6 +24,8 @@ let result = TaskResult.ofTask (task { return 42 })
2224
### Example 2
2325

2426
```fsharp
25-
let result = TaskResult.ofTask (task { return System.Exception("error") })
26-
// task { return Error (System.Exception("error")) }
27+
// Exceptions are NOT caught — they propagate out of the task
28+
let result = TaskResult.ofTask (task { return failwith "something went wrong" })
29+
// throws System.Exception("something went wrong")
2730
```
31+

src/FsToolkit.ErrorHandling/TaskResult.fs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,41 @@ module TaskResult =
244244
| Choice2Of2 ex -> Error(f ex)
245245
)
246246

247-
/// Lift Task to TaskResult
247+
/// <summary>
248+
/// Lifts a <c>Task&lt;'ok&gt;</c> into a <c>Task&lt;Result&lt;'ok, 'error&gt;&gt;</c> by wrapping the value in <c>Ok</c>.
249+
/// Any exceptions thrown by the task will not be caught and will propagate as-is.
250+
/// To catch exceptions and map them to the <c>Error</c> case, use <see cref="ofCatchTask"/>.
251+
/// </summary>
252+
/// <param name="x">The task to lift.</param>
253+
/// <returns>A task containing <c>Ok</c> of the task's result value.</returns>
248254
let inline ofTask x =
249255
x
250256
|> Task.map Ok
251257

258+
/// <summary>
259+
/// Lifts a <c>Task&lt;'ok&gt;</c> into a <c>Task&lt;Result&lt;'ok, exn&gt;&gt;</c>, catching any exceptions
260+
/// thrown by the task and wrapping them in <c>Error</c>. If the task completes successfully,
261+
/// the result is wrapped in <c>Ok</c>.
262+
/// </summary>
263+
/// <param name="x">The task to lift.</param>
264+
/// <returns>
265+
/// A task containing <c>Ok</c> of the result value if the task succeeds, or
266+
/// <c>Error</c> of the exception if the task throws.
267+
/// </returns>
268+
/// <example>
269+
/// <code>
270+
/// TaskResult.ofCatchTask (task { return 42 })
271+
/// // Returns: task { return Ok 42 }
272+
///
273+
/// TaskResult.ofCatchTask (task { failwith "something went wrong" })
274+
/// // Returns: task { return Error (System.Exception("something went wrong")) }
275+
/// </code>
276+
/// </example>
277+
let inline ofCatchTask (x: Task<'ok>) : Task<Result<'ok, exn>> =
278+
x
279+
|> Task.catch
280+
|> Task.map Result.ofChoice
281+
252282
/// Lift Result to TaskResult
253283
let inline ofResult (x: Result<_, _>) =
254284
x

tests/FsToolkit.ErrorHandling.Tests/TaskResult.fs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,29 @@ let catchTests =
730730
Expect.hasTaskErrorValueSync "unmapped" (TaskResult.catch f (toTask (Error "unmapped")))
731731
]
732732

733+
let ofCatchTaskTests =
734+
let taskThrow () =
735+
task {
736+
failwith err
737+
return Unchecked.defaultof<int>
738+
}
739+
740+
testList "TaskResult.ofCatchTask tests" [
741+
testCase "ofCatchTask returns Ok for a successful task"
742+
<| fun _ -> Expect.hasTaskOkValueSync 42 (TaskResult.ofCatchTask (task { return 42 }))
743+
744+
testCase "ofCatchTask returns Error for a throwing task"
745+
<| fun _ ->
746+
let result =
747+
TaskResult.ofCatchTask (taskThrow ())
748+
|> Async.AwaitTask
749+
|> Async.RunSynchronously
750+
751+
match result with
752+
| Error ex -> Expect.equal ex.Message err "Expected exception message to match"
753+
| Ok _ -> Tests.failtestf "Expected Error, was Ok"
754+
]
755+
733756
let zipTests =
734757
testList "TaskResult.zip tests" [
735758
testCase "Ok, Ok"
@@ -1077,6 +1100,7 @@ let allTests =
10771100
teeErrorTests
10781101
teeErrorIfTests
10791102
catchTests
1103+
ofCatchTaskTests
10801104
zipTests
10811105
zipErrorTests
10821106
TaskResultCETests

0 commit comments

Comments
 (0)