Search code examples
javascriptrecursionclojurepromiseclojurescript

Clojurescript - Recur from anonymous function promise callback


I'm working with clojurescript and localforage a promise based storage library. I have a structure set up like the following in localstorage as key value pairs

"names" : ["name1","name2","name3"]

Where then each "names" is a key for another set of values.

"name1": [val1,val2,val3]

I'm currently at the point where I have the list of names and now need to iterate through that list, get the values for each one, and then return a map with a format like:

{:name1 [val1,val2,val3] :name2 [val1,val2]}

To accomplish this, I came up with the following snippet:

(defn get-project-dates [project-map]
  "Handles getting all the times/dates for project"
  (loop [i 0
        project-dates {}]
    (if (= i (count project-map))
      project-dates
      (.then (.getItem localforage (nth project-map i)) (fn [promiseVal]
          (recur (inc i) (conj project-dates {(key (nth project-map i)) promiseVale})))))))

Unfortunately this doesn't work as instead of recur going to the loop, it will go back to the (fn). This (fn) callback is however required as the (.getItem) call returns a promise that I can't access otherwise.

My question is then is there a way to get that promise value out and recur to the loop, or a better way overall to do this?


Solution

  • There is no way that I know of to pick what you want to recur into. As far as I know, it's entirely dependant on scope.

    You could make it a straight recursive function though. There's a few ways you could set this up:

    ; Give it two argument lists. The 1-arity version is meant to be called by the user, 
    ;  while the 3-arity version is meant for recursive calls 
    (defn get-project-dates
      ([project-map i project-dates]
       (if (= i (count project-map))
         project-dates
         (.then (.getItem localforage (nth project-map i))
                (fn [promiseVal]
                  (get-project-dates
                    project-map
                    (inc i)
                    (conj project-dates {(key (nth project-map i)) promiseVale}))))))
    
      ([project-map]
       (get-project-dates project-map 0 {})))
    

    or

    (defn get-project-dates [project-map]
      ; Define a local recursive function called "rec" (or whatever)
      (letfn [(rec [i project-dates]
                (if (= i (count project-map))
                  project-dates
                  (.then (.getItem localforage (nth project-map i))
                         (fn [promiseVal]
                           (rec
                             (inc i)
                             (conj project-dates {(key (nth project-map i)) promiseVale}))))))]
    
        ; Then start the recursion off
        (rec 0 {})))
    

    The second has the benefit of not needing to pass project-map constantly, as well as a more succinct function name.

    Of course, you need to be cautious if you may have an excessive amount of recursive calls. You'll need to test to see if a lack of recur is safe.