Skip to content

Commit

Permalink
Merge pull request #210 from sogaiu/fiber-status-and-last-value-docs
Browse files Browse the repository at this point in the history
Mention fiber/status + last-value in main text
  • Loading branch information
bakpakin authored Apr 15, 2024
2 parents b3ae179 + 11d48bd commit 60192b0
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 46 deletions.
31 changes: 20 additions & 11 deletions content/docs/fibers/error_handling.mdz
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
:template "docpage.html"}
---

One of the main uses of fibers is to encapsulate and handle errors. Janet offers
no direct try/catch mechanism like some languages, and instead builds error
handling on top of fibers. When a function throws an error, Janet creates a
signal and throws that signal in the current fiber. The signal will be
propagated up the chain of active fibers until it hits a fiber that is ready to
handle the error signal. This fiber will trap the signal and return the error
from the last call to @code`resume`.
One of the main uses of fibers is to encapsulate and handle errors.
Janet offers no direct try/catch mechanism like some languages, and
instead builds error handling on top of fibers. When a function
throws an error, Janet creates a signal and throws that signal in the
current fiber. The signal will be propagated up the chain of active
fibers until it hits a fiber that is ready to handle the error signal.
This fiber will trap the signal and return the error from the last
call to @code`resume`.

@codeblock[janet]```
(defn block
Expand All @@ -28,16 +29,16 @@ from the last call to @code`resume`.
(def res (resume f))

(if (= (fiber/status f) :error)
(print "caught error: " res)
(print "value returned: " res))
(print "caught error: " res)
(print "value returned: " res))
```

## Two error-handling forms built on fibers

### @code`try`

Janet provides a simple macro @code`try` to make error-handling a bit
easier in the common case. @code`try` performs the function of a
easier in the common case. @code`try` performs the function of a
traditional try/catch block; it will execute its body and, if any
error is raised, optionally bind the error and the raising fiber in
its second clause.
Expand All @@ -48,9 +49,17 @@ its second clause.
(print "inside block...")
(error "oops")
(print "will never get here"))
([err] (print "caught error: " err)))
([err fib]
# err and (fiber/last-value fib) are the same here
(print "caught error: " err)
(print "fib's last-value: " (fiber/last-value fib))
# returns the status of fib, in this case, :error
(fiber/status fib)))
```

Note that in the above example, the caught @code`err` and `fib`'s
last-value are the same, i.e. "oops".

### @code`protect`

Janet also provides the @code`protect` macro for a slightly different
Expand Down
84 changes: 49 additions & 35 deletions content/docs/fibers/index.mdz
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,46 @@
:order 13}
---

Janet has support for single-core asynchronous programming via coroutines or
fibers. Fibers allow a process to stop and resume execution later, essentially
enabling multiple returns from a function. This allows many patterns such as
schedules, generators, iterators, live debugging, and robust error handling.
Janet's error handling is actually built on top of fibers (when an error is
thrown, control is returned to the parent fiber).

A temporary return from a fiber is called a yield, and can be invoked with the
@code[yield] function. To resume a fiber that has been yielded, use the
@code[resume] function. When @code`resume` is called on a fiber, it will only
return when that fiber either returns, yields, throws an error, or otherwise
emits a signal.

Different from traditional coroutines, Janet's fibers implement a signaling
mechanism, which is used to differentiate different kinds of returns. When a
fiber yields or throws an error, control is returned to the calling fiber. The
parent fiber must then check what kind of state the fiber is in to differentiate
errors from return values from user-defined signals.

To create a fiber, use the @code[fiber/new] function. The fiber
Janet has support for single-core asynchronous programming via
coroutines or fibers. Fibers allow a process to stop and resume
execution later, essentially enabling multiple returns from a
function. This allows many patterns such as schedules, generators,
iterators, live debugging, and robust error handling. Janet's error
handling is actually built on top of fibers (when an error is thrown,
control is returned to the parent fiber).

A temporary return from a fiber is called a yield, and can be invoked
with the @code[yield] function. To resume a fiber that has been
yielded, use the @code[resume] function.

When @code[resume] is called on a fiber, it will only return when that
fiber either returns, yields, throws an error, or otherwise emits a
signal. In all of these cases a value is produced and can be obtained
via the function @code[fiber/last-value]. This function takes a fiber
as its sole argument. If a fiber has never been resumed,
@code[fiber/last-value] will return `nil`.

Different from traditional coroutines, Janet's fibers implement a
signaling mechanism, which is used to distinguish between different
kinds of returns. When a fiber yields or throws an error, control is
returned to the calling fiber. The parent fiber must then check what
kind of state the fiber is in to differentiate errors from return
values from user-defined signals. This state is referred to as the
status of a fiber and can be checked using the @code[fiber/status]
function. This function takes a fiber as its sole argument.

To create a fiber, use the @code[fiber/new] function. The fiber
constructor takes one or two arguments. The first argument (required)
is the function that the fiber will execute. This function must accept
an arity of zero. The next argument (optional) is a collection of
flags checking what kinds of signals to trap and return via
@code[resume]. This is useful so the programmer does not need to
handle all of the different kinds of signals from a fiber. Any
is the function that the fiber will execute. This function must
accept an arity of zero. The next argument (optional) is a collection
of flags checking what kinds of signals to trap and return via
@code[resume]. This is useful so the programmer does not need to
handle all of the different kinds of signals from a fiber. Any
untrapped signals are simply propagated to the previous calling fiber.

Note that the terms "block", "mask", and "capture" are sometimes used
to refer to the "trapping" of signals.

@codeblock[janet](```
(def f (fiber/new (fn []
(yield 1)
Expand All @@ -41,16 +53,18 @@ untrapped signals are simply propagated to the previous calling fiber.
5)))

# Get the status of the fiber (:alive, :dead, :debug, :new, :pending, or :user0-:user9)
(print (fiber/status f)) # -> :new

(print (resume f)) # -> prints 1
(print (resume f)) # -> prints 2
(print (resume f)) # -> prints 3
(print (resume f)) # -> prints 4
(print (fiber/status f)) # -> :pending
(print (resume f)) # -> prints 5
(print (fiber/status f)) # -> :dead
(print (resume f)) # -> throws an error because the fiber is dead
(fiber/status f) # -> :new
(fiber/last-value f) # -> fiber hasn't been resumed yet, so returns nil

(resume f) # -> 1
(fiber/last-value f) # -> 1 (again)
(resume f) # -> 2
(resume f) # -> 3
(resume f) # -> 4
(fiber/status f) # -> :pending
(resume f) # -> 5
(fiber/status f) # -> :dead
(resume f) # -> throws an error because the fiber is dead
```)

## Using fibers to capture errors
Expand Down

0 comments on commit 60192b0

Please sign in to comment.