cider

Macroz 2025-01-24T08:49:50.897669Z

Today I've been developing some ClojureScript browser code that returns a promise. I typically C-x C-e to eval statements and get the response inside Emacs. I would like to look at the return value (e.g. browser fetches a response from API) but that is a promise. I suppose there is no technical stopper why CIDER couldn't wait that promise and then show me the value but it doesn't. Is there some existing solution or work-in-progress? And no, it's not quite the same to print the stuff in the browser console, browser window, tap> into Portal etc. I do that if there are no other good ideas. I'm iterating the code in Emacs, I'd like to be fully there, fullscreen. Those other tools maybe work for some people, but I don't feel like it is the best developer experience for Emacs users.

👀 1
solf 2025-01-24T09:50:37.314689Z

I don't think there is, and it would probably have too many edge cases to have it bundled in cider. However it shouldn't be hard to make something work for your specific use-case. For example:

(defun my-cider-eval-with-deref ()
  (interactive)
  (cider-eval-last-sexp)
  (let ((val (cider-last-sexp)))
    (when (string-match-p "\\(future\\|delay\\|promise\\)" val)
      (cider-interactive-eval 
       (concat "@" val)
       (cider-interactive-eval-handler (current-buffer) (point))))))
When calling this function after this clojure code:
(delay (Thread/sleep 2000) 42)
It will first display the delay then the 42.

👍 1
solf 2025-01-24T09:51:07.255319Z

Macroz 2025-01-24T10:34:59.026869Z

So you are testing this with Clojure? That doesn't look like it would work in CLJS

solf 2025-01-24T10:37:32.032569Z

Babashka actually 😅. Why wouldn't it work with cljs? You do need to adapt what the string-match-p looks for, and maybe how you deref the promise

Macroz 2025-01-24T10:38:05.436789Z

This detail is in "how you deref the promise".

Macroz 2025-01-24T10:41:13.295619Z

There is no deref in JS promises. There is .then but then you would have to deliver the value to some place and inspect that some place later. Or if you were in JS you'd like to use await and then do something with the value.

solf 2025-01-24T10:42:06.799069Z

(concat "(.then " val " (fn [val] (def *deref-promise val))) *deref-promise")
Maybe? I forgot if you can do inline def in clojurescript, but if you can't then you can just use an atom for example

solf 2025-01-24T10:42:23.123869Z

Basically you would eval

(.then a-promise
       (fn [val] (def *deref-promise val)))
*deref-promise

Macroz 2025-01-24T10:45:12.102759Z

But you will get the promise? Your fn should deliver the value somewhere and later you need to look at the value.

Macroz 2025-01-24T10:50:38.901449Z

(cider-last-sexp) is always something that returns a promise, and .then also returns a promise, so the return value will always be the promise. Unless in elisp one waits for something to happen to the promise via .then or so and only returns execution after that with the proper value.

solf 2025-01-24T11:01:12.237799Z

Hmm you're right, I'll fire up a cljs repl and think about it. It's been a while since I've used promises

Macroz 2025-01-24T11:02:20.034389Z

I'm sure it's not impossible for someone who is more familiar with elisp and what CIDER already provides to loop it or so

oyakushev 2025-01-24T13:47:32.372979Z

It's quite plausible that if you give this problem to ChatGPT, it will come back with some workaround. Something like what @dromar56 suggested, but with a .then callback that will eventually echo the result into the minibuffer.

solf 2025-01-24T13:49:40.713609Z

That's pretty much what I already have, as in using this:

(concat "(.then " val " (fn [val] (def *deref-promise val))) *deref-promise")
Will show the 42 if you eval (js/Promise.resolve 42)

oyakushev 2025-01-24T13:53:11.214589Z

I don't this works the way you think it works.

solf 2025-01-24T13:53:15.821729Z

But if you eval something like:

(-> (js/Promise.resolve 42)
      (.then (fn [val] (do-something-with-val))))
That then with do-something will get the value of the resolve first, and not pass it back. In this case what we would need is to somehow add our own then first, like:
(-> (js/Promise.resolve 42)
      (.then (fn [val] (def *dered-promise val) val))
      (.then (fn [val] (do-something-with-val))))
But even this wouldn't work in a general way, as we could be evaluating (fn-that-returns-promise-with-a-then), and then we can't interpose our then

solf 2025-01-24T13:54:56.565849Z

I don't this works the way you think it works.It's possible, but what do you mean in particular?

oyakushev 2025-01-24T13:55:19.116119Z

Try it with some delay.

oyakushev 2025-01-24T13:56:39.007029Z

My Javascript is very rusty to know how to do that 😅

Macroz 2025-01-24T14:01:25.480409Z

I think there are actual CIDER experts on this channel so I don't have to try to shepherd a stochastic parrot 🙂

🤷 1
oyakushev 2025-01-24T14:01:29.406089Z

Here's your example here https://clojurescript.io/:

Macroz 2025-01-24T14:01:48.327709Z

That just doesn't do it

Macroz 2025-01-24T14:02:36.367229Z

So how one goes about polling the CLJS-world to figure out that the promise has been resolved

oyakushev 2025-01-24T14:10:09.493769Z

You can try shepherding the parrot towards a PR so that others benefit from it too! 🙂

oyakushev 2025-01-24T14:12:52.395589Z

BTW, curiously it goes inline with https://clojurians.slack.com/archives/C0617A8PQ/p1737400698101539. If there were a way to show print results in the code buffer, then printing the promise on callback would be an option too.

Macroz 2025-01-24T14:14:11.581849Z

Sure, it should be orthogonal where it is printed but IMHO all the eval functions are a mess.

oyakushev 2025-01-24T14:15:00.729949Z

What's messy about them? You mean that there're too many specialized eval functions?

☝️ 1
solf 2025-01-24T14:43:51.625529Z

Well... we don't have to wait for it in cljs 🤷 but yeah, hopefully someone can chime in with a cleaner solution.

(defun wait-for-promise-resolution ()
  (while (equal (nrepl-dict-get (cider-nrepl-sync-request:eval "(= *p :unresolved)") "value")
                "true")
    (message "Not yet resolved")
    (sit-for 0.1))
  (message "Resolved: %s" (nrepl-dict-get (cider-nrepl-sync-request:eval "*p") "value")))

(defun my-cider-eval-with-deref ()
  (interactive)
  (cider-eval-last-sexp)
  (let ((val (cider-last-sexp)))
    (when (string-match-p "\\(future\\|delay\\|promise\\)" val)
      (cider-nrepl-sync-request:eval 
       (concat "(def *p :unresolved) (.then " val " (fn [val] (def *p val)))"))
      (wait-for-promise-resolution)
      (cider-interactive-eval "*p"
                             (cider-interactive-eval-handler (current-buffer) (point))))))

👍 2
solf 2025-01-24T14:46:24.612769Z

Macroz 2025-01-24T14:56:41.222439Z

I wrote something similar but didn't know of sit-for

Macroz 2025-01-24T15:25:05.012159Z

I think your version seems to not work in JS (because it won't match "Promise"), and it will also print the "promise" inline in the buffer and print the actual value in the minibuffer?

solf 2025-01-24T15:25:57.281529Z

the runtime is chrome, via shadow-cljs

Macroz 2025-01-24T15:26:41.987369Z

same for me

solf 2025-01-24T15:26:42.325759Z

but it might not work depending on what exactly you are evaluating. For example if instead of that js block that creates promises, you eval a function that does, it might not work indeed

Macroz 2025-01-24T15:26:57.639219Z

js/fetch currently, but wrapped by lambdaisland.fetch

solf 2025-01-24T15:27:28.922679Z

If you eval it normally what does cider print? You would match it against that

Macroz 2025-01-24T15:28:19.548069Z

#<js/Promise[~]>

Macroz 2025-01-24T15:29:10.735309Z

if I just hard-code it to match then it works with the effect of printing slightly differently than the normal eval

Macroz 2025-01-24T15:29:55.008079Z

The problem with these different eval commands is that one must override all of them that one ever uses 😅

solf 2025-01-24T15:38:35.029299Z

Weird, the string match does seem to work: (string-match-p "\\(future\\|delay\\|promise\\)" "#<js/Promise[~]>") => 5. It's still very brittle, you should probably bind it to a specific key and use it only when you know you're evaluating a promise. Or use nrepl-sync-request and check that the object is a promise with:

const isPromise = v => typeof v === 'object' && typeof v.then === 'function'
(apparently having a .then method is the only thing that differentiates a promise)

Macroz 2025-01-24T15:43:12.423389Z

Yeah, that's easier to work around but outputting the result is the other niggle.

Macroz 2025-01-24T15:43:31.753479Z

The regular does this

(cider-interactive-eval "*p"
                              (when output-to-current-buffer (cider-eval-print-handler))
                              (cider-last-sexp 'bounds)
                              (cider--nrepl-pr-request-map))

Macroz 2025-01-24T15:43:41.826879Z

So the result ends up in the buffer with the file

Macroz 2025-01-24T15:44:03.056609Z

However my attempts to use that pattern instead actually don't show results until I move a character

Macroz 2025-01-24T15:44:41.880789Z

I don't understand how one is supposed to compose these

solf 2025-01-24T15:45:17.460319Z

If you're passing a string to cider-interactive-eval you have to pass the eval handler explicitely, like this:

(cider-interactive-eval "*p"
                             (cider-interactive-eval-handler (current-buffer) (point)))

solf 2025-01-24T15:46:18.531909Z

Because usually you don't pass a string, you pass a position in the buffer, so the eval-handler knows where to display the overlay. If you pass a string then it's up to you to tell it where is the point

Macroz 2025-01-24T15:46:39.781829Z

But that doesn't actually do it

Macroz 2025-01-24T15:46:49.259849Z

It only shows up in the minibuffer

Macroz 2025-01-24T15:47:43.642099Z

The #<js/Promise[~]> ends up inline

solf 2025-01-24T15:50:09.130699Z

I don't know what's the difference in our setups, it does appear at the right place (as in my last gif)

Macroz 2025-01-24T15:53:12.306759Z

Are you running Emacs 29?

Macroz 2025-01-24T15:53:23.847509Z

This is what it looks like for me

solf 2025-01-24T15:58:50.848139Z

Ah that's what I thought you wanted 😅 Having the result similarly to where C-x C-e would put it

Macroz 2025-01-24T15:59:03.061649Z

But it doesn't actually do that

solf 2025-01-24T15:59:08.572919Z

It does show first the js/Promise, until it resolves

Macroz 2025-01-24T15:59:19.700189Z

Yeah, that's not a problem if the result later replaces it

Macroz 2025-01-24T15:59:37.195279Z

But the result goes to minibuffer instead, and if I move a character then it appears in the buffer too

Macroz 2025-01-24T16:00:48.465069Z

Also it's not highlighted so something isn't working

solf 2025-01-24T16:00:49.119859Z

Ah I see. I wrote the result in the message just for debugging purposes, you can remove this line: (message "Resolved: %s" (nrepl-dict-get (cider-nrepl-sync-request:eval "*p") "value")) As for why you need to move a character... I'm not sure

✅ 1
Macroz 2025-01-24T16:02:31.044609Z

So now it's just missing the highlighting

Macroz 2025-01-24T16:02:44.976429Z

Removing messages fixed the need to move

Macroz 2025-01-24T16:05:38.943329Z

Looks like that inspector isn't liking it either

Inspector error for: {:status 200, :headers {"cache-control" "no-store", "content-length" "3544", "content-type" "application/transit+json; charset=utf-8"}, :body [{:application/workflow {:workflow/id 1, :workflow/type :workflow/default}, :application/external-id  ...

solf 2025-01-24T16:07:04.771859Z

Inspector doesn't work for cljs

Macroz 2025-01-24T16:13:52.416449Z

I guess not, but the highlighting should

Macroz 2025-01-24T16:21:35.133759Z

Ah ok now I understand the matching. (cider-last-sexp) is the code, not the return value. One should match against the return value?

solf 2025-01-24T16:22:21.330549Z

Ah you're right, that's a mistake

Macroz 2025-01-24T16:23:25.500239Z

Thanks anyway so far. Have to think of how to compose this best with the other evals. I typically eval only inline or other buffer.

👍 1
Daniel Slutsky 2025-01-24T18:45:11.682609Z

https://clojurians.slack.com/archives/C8NUSGWG6/p1737744185185759

👍 2
👍🏻 1