Skip to content

Commit

Permalink
Large amount of changes/updates
Browse files Browse the repository at this point in the history
attach-errback now returns a promise, finished with the return value of
the errback.

introducing `promisify` which turns any val (or multiple values) into a
promise object, catching errors as it goes along:

  (promisify (values 1 2 3))      => promise with values (1 2 3)
  (promisify (error "oh crap"))   => promise with errored T

improving error chaining. previously, errors were not propagating like
values were. this has been updated so that an errback at the bottom (or
top in lisp) of the chain can catch errors for the entire chain.

introducing `catcher` macro, which essentially replaces
promise-handler-case. instead of wrapping lexical forms, we now rely on
error chaining. this is nicer because we dont just need access to the
lexical form, any other form that returns a promise can be caaught.

introducing `finally` macro, which runs its body form whether the given
promise is finished or errored, allowing for easy cleanup in a chain no
matter the outcome.

introducing `with-promise` macro. separates promise creation and
resolving from consumption:

  (with-promise (resolve reject)
    (as:with-delay (1)
      (resolve 123)))

this is important not just because of separation of creation and
consumption, but because with-promise catches errors in the creation
process and signals them on the returned promise.

adding utility functions:

- amap
- all
- areduce

adding chaining helper:

(chain 4
  (:then (x) (+ 4 x))
  (:then (x) (format t "x is ~a~%" x)))

yields form:

  (attach (promisify (attach (promisify 4)
                             (lambda (x) (+ 4 x))))
          (lambda (x) (format t "x is ~a~%" x)))

(chain 4
  (:then (x) (+ x 9))
  (:then (x) (list x x x))
  (:map (x) (+ x 5))
  (:reduce (acc x 0) (+ acc x))
  (:then (final) (format t "value is ~a~%" final))
  (:catch (e) (format t "error! ~a~%" e)))

prints:
  value is 54

(chain 4
  (:then (x) (* x 13))
  (:then (y) (list y (1+ y) (+ 5 y)))
  (:map (x) (+ x 'tst))
  (:then (x) (format t "x is ~a~%" x))
  (:catch (e) (format t "error! ~a~%" e))
  (:finally () (format t "done~%")))

prints:
  error! The value TST is not of the expected type NUMBER.
  done

introducing the concept of naming promises, although this will probably
go away and be replaced with something a bit more helpful.
  • Loading branch information
orthecreedence committed Nov 30, 2014
1 parent 0bc19cb commit 4347ed6
Show file tree
Hide file tree
Showing 6 changed files with 553 additions and 368 deletions.
4 changes: 3 additions & 1 deletion blackbird.asd
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
:depends-on ()
:components
((:file "package")
(:file "promise" :depends-on ("package"))))
(:file "promise" :depends-on ("package"))
(:file "syntax" :depends-on ("package" "promise"))
(:file "error" :depends-on ("package" "promise" "syntax"))))

110 changes: 110 additions & 0 deletions error.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
;;; NOTE: all these utilities are now obsolete. Please use catcher instead of
;;; promise-handler-case!!

(in-package :blackbird)

(defmacro %handler-case (body &rest bindings)
"Simple wrapper around handler-case that allows switching out the form to make
macroexpansion a lot easier to deal with."
`(handler-case ,body ,@bindings))

(defmacro wrap-event-handler (promise-gen error-forms)
"Used to wrap the promise-generation forms of promise syntax macros. This macro
is not to be used directly, but instead by promise-handler-case.
It allows itself to be recursive, but any recursions will simply add their
error forms for a top-level list and return the form they are given as the
body. This allows a top-level form to add an error handler to a promise, while
gathering the lower-level forms' handler-case bindings into one big handler
function (created with make-nexted-handler-cases).
Note that since normally the wrap-event-handler forms expand outside in, we
have to do some trickery with the error-handling functions to make sure the
order of the handler-case forms (as far as what level of the tree we're on)
are preserved."
(let ((signal-error (gensym "signal-error"))
(handler-fn (gensym "handler-fn"))
(vals (gensym "vals")))
;; hijack any child wrap-event-handler macros to just return their
;; promise-gen form verbatim, but add their error handlers to the error
;; handling chain
`(macrolet ((wrap-event-handler (promise-gen error-forms)
(let ((old-signal-error (gensym "old-signal-error")))
`(progn
;; "inject" the next-level down error handler in between the
;; error triggering function and the error handler one level
;; up. this preserves the handler-case tree (as opposed to
;; reversing it)
;; NOTE that signal-error is defined *below* in the body
;; of the macrolet form
(let ((,old-signal-error ,',signal-error))
(setf ,',signal-error
(lambda (ev)
(%handler-case
(funcall ,old-signal-error ev)
,@error-forms))))
;; return the promise-gen form verbatim
,promise-gen))))
;; define a function that signals the error, and a top-level error handler
;; which uses the error-forms passed to THIS macro instance. any instance
;; of `wrap-event-handler` that occurs in the `promise-gen` form will inject
;; its error handler between handler-fn and signal-error.
(let* ((,signal-error (lambda (ev) (error ev)))
(,handler-fn (lambda (ev)
(%handler-case
(funcall ,signal-error ev)
,@error-forms)))
;; sub (wrap-event-handler ...) forms are expanded with ,promise-gen
;; they add their handler-case forms into a lambda which is injected
;; into the error handling chain,
(,vals (multiple-value-list ,promise-gen)))
(if (promisep (car ,vals))
(progn
(attach-errback (car ,vals) ,handler-fn)
(car ,vals))
(apply #'values ,vals))))))

(defmacro promise-handler-case (body-form &rest error-forms &environment env)
"Wrap all of our lovely attach macro up with an event handler. This is more or
less restricted to the form it's run in.
Note that we only have to wrap (attach) because *all other syntax macros* use
attach. This greatly simplifies our code.
Note that if we just wrap `attach` directly in a macrolet, it expands
infinitely (probably no what we want). So we're doing some trickery here. We
use the environment from the top-level macro to grab the original macro
function and make it available from *within* the macrolet. This allows
the macrolet to redefine the `attach` macro while also simultaneously
expanding the previous definition of it. This allows wrapped calls of
promise-handler-case to add layers of error handling around any `attach` call
that is within lexical grasp."
(if (or (find :promise-debug *features*)
(find :future-debug *features*))
;; we're debugging promises...disable all error handling (so errors bubble
;; up to main loop)
body-form
;; wrap the top-level form in a handler-case to catch any errors we may
;; have before the promises are even generated.
`(%handler-case
;; redefine our attach macro so that the promise-gen forms are
;; wrapped (recursively, if called more than once) in the
;; `wrap-event-handler` macro.
(macrolet ((attach (promise-gen fn &environment ml-env)
(let ((args (gensym "phc-wrap-args")))
;; call the original attach macro (via our pass env).
;; this allows calling it without throwing macrolet
;; into an endless loop
(funcall (macro-function 'attach ',env)
`(attach
(wrap-event-handler ,promise-gen ,',error-forms)
;; create a wrapper function around the given
;; callback that applies our error handlers
(lambda (&rest ,args)
(%handler-case
(apply ,fn ,args)
,@',error-forms)))
ml-env))))
,body-form)
,@error-forms)))

8 changes: 8 additions & 0 deletions package.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@
(:export #:promise
#:promise-finished-p
#:make-promise
#:with-promise

#:attach-errback
#:lookup-forwarded-promise
#:signal-error
#:promisep
#:finish
#:reset-promise

#:attach
#:catcher
#:finally

#:chain
#:alet
#:alet*
#:aif
#:multiple-promise-bind
#:wait
#:adolist

#:promise-handler-case

#:*promise-keep-specials*))
Loading

0 comments on commit 4347ed6

Please sign in to comment.