Why does let-defined atom provide a different result?

134 Views Asked by At

I try to deasync some js-calls using clojure script. If I let the atom in the deasync function itself there is a suprising result:

(let [result (atom nil)
      timeout (atom 10)]
  (go
    (let [async-result (<p! (js/Promise.resolve 42))]
        (swap! result (constantly async-result))
        (js/console.log @result)))
  (while (and (nil? @result)
             (pos? @timeout))
    (do (debug @result)
        (debug @timeout)
        (swap! timeout dec)))
  @result)
 
--- output is ---
null
10
...
null
1
42
nil

If I define the atom outside the result is as intended:

(def result (atom nil))
 
(let [timeout (atom 10)]
  (go
    (let [async-result (<p! (js/Promise.resolve 42))]
        (swap! result (constantly async-result))
        (js/console.log @result)))
  (while (and (nil? @result)
             (pos? @timeout))
    (do (debug @result)
        (debug @timeout)
        (swap! timeout dec)))
  @result)

--- output is ---
42
42

Can someone declare the difference? I think using a namespace global atom is not quite a good idea for a deasync function ...

1

There are 1 best solutions below

2
Thomas Heller On

It is not possible to write a "deasync" function, if by that you mean writing a function that "blocks" to wait for an async result. JavaScript is single-threaded so if you block in a loop to wait for an async result that loop will prevent the async work from ever happening.

The result in your program above will always be that it loops 10 times while the result is nil and then eventually yield control to let the queued microtask execute the go.

I suspect that you just maybe tried to run this from the REPL and did not redefine the result atom after one test run. So in the first pass it was nil but then got set to 42. On the second run it then starts with 42 and your loop never loops. You can easily verify this by "labeling" your results. So instead of (js/console.log @result) you use (js/console.log "go-result" @result). You'll see that you'll always get the loop result first and then the go-result.

BTW if you just want to set a specific atom value just use reset! instead of swap! with constantly, eg. (reset! result async-result).