You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/deferred.md
+21Lines changed: 21 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -166,6 +166,27 @@ In this example, `c` is declared within a normal `let` binding, and as such we c
166
166
167
167
It can be helpful to think of `let-flow` as similar to Prismatic's [Graph](https://github.com/prismatic/plumbing#graph-the-functional-swiss-army-knife) library, except that the dependencies between values are inferred from the code, rather than explicitly specified. Comparisons to core.async's goroutines are less accurate, since `let-flow` allows for concurrent execution of independent paths within the bindings, whereas operations within a goroutine are inherently sequential.
168
168
169
+
### manifold.go-off
170
+
171
+
An alternate way to write code using deferreds is the macro `manifold.go-off/go-off`. This macro is an almost exact mirror of the `go` macro from [clojure/core.async](https://github.com/clojure/core.async), to the point where it actually utilizes the state machine functionality from core.async. In order to use this macro, `core.async` must be a dependency provided by the user. The main difference between `go` and `go-off`, besides go-off working with deferrables instead of core.async channels, is the `take` function being `<!?` instead of `<!`. The difference in function names is used to indicate exceptions behave the same as a non-async clojure block (i.e. are thrown) instead of silently swallowed & returning `nil`.
172
+
173
+
The benefit of this macro over `let-flow` is that it gives complete control of whendeferreds should be realized to the user of the macro, removing any potential surprises (especially around timeouts).
174
+
175
+
```clj
176
+
@(go-off (+ (<!? (d/future10))
177
+
(<!? (d/future20)))) ;; ==> 30
178
+
```
179
+
180
+
```clj
181
+
(<!! (core.async/go (try (<! (go (/50)))
182
+
(catch Exception e
183
+
"ERROR")))) ; ==> nil
184
+
185
+
@(go-off (try (<!? (d/future (/50)))
186
+
(catch Exception e
187
+
"ERROR"))) ; ==> "ERROR"
188
+
```
189
+
169
190
### `manifold.deferred/loop`
170
191
171
192
Manifold also provides a `loop` macro, which allows for asynchronous loops to be defined. Consider `manifold.stream/consume`, which allows a function to be invoked with each new message from a stream. We can implement similar behavior like so:
:doc"Provide a variant of `core.async/go` that works with manifold's deferreds and executors. Utilizes core.async's state-machine generator, so core.async must be provided as a dependency."}
3
+
manifold.go-off
4
+
(:require [manifold
5
+
[executor :as ex]
6
+
[deferred:as d]]
7
+
[clojure.core.async.impl
8
+
[ioc-macros :as ioc]]
9
+
[manifold.stream :as s])
10
+
(:import (java.util.concurrent Executor)
11
+
(manifold.stream.core IEventSource)))
12
+
13
+
(defnreturn-deferred [state value]
14
+
(let [d (ioc/aget-object state ioc/USER-START-IDX)]
15
+
(d/success! d value)
16
+
d))
17
+
18
+
(defn<!
19
+
"Takes value from a deferred/stream. Must be called inside a (go ...) block. Will
20
+
return nil if a stream is closed. Will park if nothing is available. If an error
21
+
is thrown inside the body, that error will be placed as the return value.
22
+
23
+
N.B. To make `go-off` usage idiomatic with the rest of manifold, use `<!?`
24
+
instead."
25
+
[port]
26
+
(assertnil"<! used not in (go-off ...) block"))
27
+
28
+
(defmacro<!?
29
+
"Takes a val from a deferred/stream. Must be called inside a (go-off ...) block.
30
+
Will park if nothing is available. If value that is returned is a Throwable,
31
+
it will re-throw."
32
+
[port]
33
+
`(let [r# (<! ~port)]
34
+
(if (instance? Throwable r#)
35
+
;; this is a re-throw of the original throwable. the expectation is that
36
+
;; it still will maintain the original stack trace
37
+
(throw r#)
38
+
r#)))
39
+
40
+
(defnrun-state-machine-wrapped [state]
41
+
(try (ioc/run-state-machine state)
42
+
(catch Throwable ex
43
+
(d/error! (ioc/aget-object state ioc/USER-START-IDX) ex)
44
+
(throw ex))))
45
+
46
+
(defntake! [state blk d]
47
+
(let [handler (fn [x]
48
+
(ioc/aset-all! state ioc/VALUE-IDX x ioc/STATE-IDX blk)
49
+
(run-state-machine-wrapped state))
50
+
;; if `d` is a stream, use `take` to get a deferrable that we can wait on
51
+
d (if (instance? IEventSource d) (s/take! d) d)
52
+
d-is-deferrable? (d/deferrable?d)]
53
+
(if
54
+
;; if d is not deferrable immediately resume processing state machine
55
+
(not d-is-deferrable?)
56
+
(do (ioc/aset-all! state ioc/VALUE-IDX d ioc/STATE-IDX blk)
57
+
:recur)
58
+
(let [d (d/->deferred d)]
59
+
(if
60
+
;; if already realized, deref value and immediately resume processing state machine
61
+
(d/realized? d)
62
+
(do (ioc/aset-all! state ioc/VALUE-IDX @d ioc/STATE-IDX blk)
63
+
:recur)
64
+
65
+
;; resume processing state machine once d has been realized
66
+
(do (-> d
67
+
(d/chain handler)
68
+
(d/catch handler))
69
+
nil))))))
70
+
71
+
(defasync-custom-terminators
72
+
{'manifold.go-off/<! `manifold.go-off/take!
73
+
:Return `return-deferred})
74
+
75
+
(defmacrogo-off-executor
76
+
"Implementation of go-off that allows specifying executor. See docstring of go-off for usage."
0 commit comments