Search code examples
return-valuefilereaderclojurescript

How to return the result of FileReader. in clojurescript


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...


Solution

  • 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! :))