Last active
October 24, 2021 00:55
-
-
Save pesterhazy/74dd6dc1246f47eb2b9cd48a1eafe649 to your computer and use it in GitHub Desktop.
Promise chains in ClojureScript and the problem of previous values
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns my.promises | |
"Demo to show different approaches to handling promise chains in ClojureScript | |
In particular, this file investigates how to pass data between Promise | |
callbacks in a chain. | |
See Axel Rauschmayer's post | |
http://2ality.com/2017/08/promise-callback-data-flow.html for a problem | |
statement. | |
The examples is this: based on n, calculate (+ (square n) n), but with each step | |
calculated asynchronously. The problem for a Promise-based solution is that the | |
sum step needs access to a previous value, n. | |
Axel's solution 1 is stateful and not idiomatic in Clojurescript. | |
Solution 1 (nested scopes) is implemented in test3. | |
Solution 2 (multiple return values) is implemented in test1 and test2. | |
For reference, a synchronous implementation is implemented in test0." | |
(:refer-clojure :exclude [resolve])) | |
(enable-console-print!) | |
;; helpers for working with promises in CLJS | |
(defn every [& args] | |
(js/Promise.all (into-array args))) | |
(defn soon | |
"Simulate an asynchronous result" | |
([v] (soon v identity)) | |
([v f] (js/Promise. (fn [resolve] | |
(js/setTimeout #(resolve (f v)) | |
500))))) | |
(defn resolve [v] | |
(js/Promise.resolve v)) | |
;; helpers | |
(defn square [n] (* n n)) | |
;; test0 | |
(defn test0 | |
"Synchronous version - for comparison | |
The code has three steps: | |
- get value for n | |
- get square of n | |
- get sum of n and n-squared | |
Note that step 3 requires access to the original value, n, and to the computed | |
value, n-squared." | |
[] | |
(let [n 5 | |
n-squared (square 5) | |
result (+ n n-squared)] | |
(prn result))) | |
;; test1 | |
(defn square-step [n] | |
(soon (every n (soon n square)))) | |
(defn sum-step [[n squared-n]] ;; Note: CLJS destructuring works with JS arrays | |
(soon (+ n squared-n))) | |
(defn test1 | |
"Array approach, flat chain: thread multiple values through promise chain by using Promise.all" | |
[] | |
(-> (resolve 5) | |
(.then square-step) | |
(.then sum-step) | |
(.then prn))) | |
;; test2 | |
(defn to-map-step [array] | |
(zipmap [:n :n-squared] array)) | |
(defn sum2-step [{:keys [n n-squared] :as m}] | |
(soon (assoc m :result (+ n n-squared)))) | |
(defn test2 | |
"Accumulative map approach, flat chain: add values to CLJS map in each `then` step, making | |
it possible for later members of the chain to access previous results" | |
[] | |
(-> (resolve 5) | |
(.then square-step) | |
(.then to-map-step) | |
(.then sum2-step) | |
;; Note: `(.then :result)` doesn't work because `:result` is not | |
;; recognized as a function. So we need to wrap it in an anon fn. | |
;; This could be easily fixed by adding a CLJS `then` function that | |
;; has a more inclusive notion of what a function is. | |
(.then #(:result %)) | |
(.then prn))) | |
;; test3 | |
(defn square-step-fn [n] | |
;; This could be called a "resolver factory" fn. It's a higher-order function | |
;; that returns a resolve function. `n` is captured in a closure. | |
(fn [n-squared] | |
(soon (+ n n-squared)))) | |
(defn square-and-sum-step [n] | |
(-> (soon (square n)) | |
;; note that square-step-fn is _called_ here, not referenced, in order to | |
;; provide its inner body with access to the previous result, `n`. | |
(.then (square-step-fn n)))) | |
(defn test3 | |
"Nested chain approach: instead of a flat list, use a hierarchy, nesting one Promise chain in another. | |
Uses a closure to capture the intermediate result, `n`, making it available to the nested chain." | |
[] | |
(-> (resolve 5) | |
(.then square-and-sum-step) | |
(.then prn))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See http://2ality.com/2017/08/promise-callback-data-flow.html and https://stackoverflow.com/questions/28250680/how-do-i-access-previous-promise-results-in-a-then-chain