Skip to content

Commit

Permalink
πŸ“ document error boundaries and the scoped() function
Browse files Browse the repository at this point in the history
The way error boundaries are created has changed from `v3` to `v4`.
Instead of using `call()` or `action()` to establish an error
boundary, there is now a dedicated `scoped()` function which not only
traps any errors, but also creates a resource boundary that does not
let any resource or task escape.

This updates the error documentation, and also adds documentation to
the `scoped()` function. Finally, there was an old deno link that I
updated to JSR.
  • Loading branch information
cowboyd committed Jan 8, 2025
1 parent 9c3717d commit 101b860
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 13 deletions.
12 changes: 12 additions & 0 deletions lib/scoped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ import { useCoroutine } from "./coroutine.ts";
* shut down, and all contexts will be restored to their values outside
* of the scope.
*
* @example
* ```js
* import { useAbortSignal } from "effection";
* function* example() {
* let signal = yield* scoped(function*() {
* return yield* useAbortSignal();
* });
* return signal.aborted; //=> true
* }
* ```
*
* @param operation - the operation to be encapsulated
*
* @returns the scoped operation
Expand Down
28 changes: 15 additions & 13 deletions www/docs/errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ async function runExample() {
await task.halt();

try {
let value = await task // πŸ’₯ throws "halted" error;

let value = await task // πŸ’₯ throws "halted"
// will never reach here because halted tasks do not produce values
console.log(value);
} catch(err) {
Expand Down Expand Up @@ -127,9 +126,9 @@ import { tickingBomb } from './ticking-bomb';
await main(function*() {
yield* spawn(tickingBomb);
try {
yield* suspend(); // sleep forever
yield* suspend(); // sleep forever
} catch(err) {
console.log("it blew up:", err.message);
console.log("it blew up:", err.message);
}
});
```
Expand All @@ -138,30 +137,33 @@ You might be surprised that we do *not* enter the catch handler here. Instead,
our entire main task just fails. This is by design! We are only allowed to
catch errors thrown by whatever we yield to directly, _not_ by any spawned
children or resources running in the background. This makes error handling more
predictable, since our catch block will not receive errors from any background
predictable. Since our catch block will not receive errors from any background
task, we're better able to specify which errors we actually want to deal with.

## Error boundary

If we do want to catch an error from a spawned task (or from a [Resource][]) then
we need to introduce an intermediate task which allows us to bring the error into
the foreground. We call this pattern an "error boundary":
If we do want to catch an error that happens in the background, then
we need to explicitly tell Effection where it should be brought into the foreground.
This location is called an "error boundary". To establish an error boundary you can
wrap any operation with the `scoped()` function.

``` typescript
import { main, call, spawn, suspend } from 'effection';
import { main, scoped, spawn, suspend } from 'effection';
import { tickingBomb } from './ticking-bomb';

main(function*() {
await main(function*() {
try {
yield* call(function*() { // error boundary
yield* scoped(function*() { // error boundary
yield* spawn(tickingBomb); // will blow up in the background
yield* suspend(); // sleep forever
yield* suspend(); // sleep forever
});
} catch(err) {
console.log("it blew up:", err.message);
}
});
```

Now, when our spawned task throws an error, control will pass to our catch handler.

[Resource]: ./resources
[task]: https://deno.land/x/effection/mod.ts?s=Task
[task]: https://jsr.io/@effection/effection/doc/~/Task

0 comments on commit 101b860

Please sign in to comment.