0

I have a function that reads a file using js/FileReader.:

(defn read-file [file]
  (let [js-file-reader (js/FileReader.)]
    (set! (.-onload js-file-reader)
      (fn [evt]
        (let [result (-> evt .-target .-result)
              array (js/Uint8Array. result)]
          {:content array}))) ; <- This is the value that 'read-file' should return
    (.readAsArrayBuffer js-file-reader file)))

The problem is that I would like it to return the value of the .-onload method of the FileReader, but I only get (of course) the value of (.readAsArrayBuffer js-file-reader file) which, naturally, is undefined.

Thank you very much!

Edit

After trying with Martin Půda's answer, I think that the problem has to do with an asyncrhonous thing. I tested this code:

(defn read-file [file]
  (let [js-file-reader (js/FileReader.)
        reading-result (atom)
        done? (atom false)]
    (set! (.-onload js-file-reader)
      (fn [evt]
        (let [result (-> evt .-target .-result)
              array (js/Uint8Array. result)]
          (reset! reading-result {:content array})
          (reset! done? true)
          (js/console.log "in: " (:content @reading-result)))))
    (.readAsArrayBuffer js-file-reader file)
;;     (while (not @done?) (js/console.log (.-readyState js-file-reader)))
    (js/console.log "out: " @reading-result)
    @reading-result))

I get first the log of out: undefined , and then the log of in: (with the desired result).

When I uncomment the line (while...), I get an infinite loop of 1's... So I think that the function never notices that the FileReader was done... I don't know how to solve this...

Luis López
  • 159
  • 8

2 Answers2

1

After much reading, I solved it using a callback function:

(ns lopezsolerluis.fits)

(defn read-file [file callback]
  (let [js-file-reader (js/FileReader.)]
    (set! (.-onload js-file-reader)
      (fn [evt]
        (let [result (-> evt .-target .-result)
              array (js/Uint8Array. result)]
          (callback array))))
    (.readAsArrayBuffer js-file-reader file)))

Then read-file is called from this:

(ns lopezsolerluis.annie-web)

(defn procesar-archivo [result]
  (js/console.log "Bloques: " result))

(defn input-file []
  [:input {:type "file" :id "fits" :name "imagenFits" :accept "image/fits" 
           :on-change (fn [this]
                        (if (not (= "" (-> this .-target .-value)))
                          (let [^js/File file (-> this .-target .-files (aget 0))]
                            (fits/read-file file procesar-archivo)))
                          (set! (-> this .-target .-value) ""))}])

I added the namespaces because it surprised me that the callback machinery worked even across namespaces. Well, maybe it shouldn't surprise me; but I am learning and was a new concept for me. :)

I answer my question in case it's useful for others (it costed me a lot! :))

Luis López
  • 159
  • 8
0

Try this:

(defn read-file [file]
  (let [js-file-reader (js/FileReader.)
        reading-result (atom)]
    (set! (.-onload js-file-reader)
      (fn [evt]
        (let [result (-> evt .-target .-result)
              array (js/Uint8Array. result)]
          (reset! reading-result {:content array}))))
    (.readAsArrayBuffer js-file-reader file)
    @reading-result))

See docs for atom and reset!.

EDIT: See HTML5 FileReader how to return result?- yes, it's asynchronous. I see two possibilities:

  • You will write all function calls, which work with returned value, inside event listeners.
  • You will follow this CLJS guide for Promises and use JS Promises or clojure.core.async.
Martin Půda
  • 7,353
  • 2
  • 6
  • 13
  • Thank you very much, but I'm still getting `undefined`. The weird thing is that if I use `(atom 2)` (for example) in line 3, I get `2` as a result, as if the line `(reset! ...)` never happened... – Luis López Mar 01 '22 at 17:40
  • I wonder if the function `read-file` returns _before_ the `onload` method has a chance of `reset!` the atom... – Luis López Mar 01 '22 at 17:45