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 a 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, the parent fiber will handle the error).
A temporary return from a fiber is called a yield, and can be invoked with the
To resume a fiber that has been yielded, use the
resume function. When resume is called on a fiber,
it will only return when that fiber either returns, yields, throws an error, or otherwise emits
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, user the
fiber/new function. The fiber constructor take one or two arguments.
The first, necessary argument is the function that the fiber will execute. This function must accept
an arity of zero. The next optional argument is a collection of flags checking what kinds of
signals to trap and return via
resume. This is useful so
the programmer does not need to handle all different kinds of signals from a fiber. Any un-trapped signals
are simply propagated to the next fiber.
(def f (fiber/new (fn  (yield 1) (yield 2) (yield 3) (yield 4) 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)) # -> print :pending (print (resume f)) # -> prints 5 (print (fiber/status f)) # -> print :dead (print (resume f)) # -> throws an error because the fiber is dead
Besides being used as coroutines, fibers can be used to implement error handling (exceptions).
(defn my-function-that-errors [x] (print "start function with " x) (error "oops!") (print "never gets here")) # Use the :e flag to only trap errors. (def f (fiber/new my-function-that-errors :e)) (def result (resume f)) (if (= (fiber/status f) :error) (print "result contains the error") (print "result contains the good result"))
Since the above code is rather verbose, Janet provides a
try macro which is similar to try - catch in other languages.
It wraps a body of code in a new fiber,
resumes the fiber, and
then checks the result. If the fiber has errored, an error clause
(try (error 1) ([err] (print "got error: " err))) # evaluates to nil and prints "got error: 1" (try (+ 1 2 3) ([err] (print "oops"))) # Evaluates to 6 - no error thrown