This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-10-13
Channels
- # announcements (1)
- # babashka (30)
- # beginners (43)
- # biff (24)
- # calva (72)
- # cider (12)
- # clj-commons (24)
- # clj-on-windows (10)
- # cljsrn (23)
- # clojure (123)
- # clojure-bay-area (6)
- # clojure-europe (43)
- # clojure-losangeles (2)
- # clojure-nl (2)
- # clojure-uk (9)
- # clojurescript (125)
- # core-async (9)
- # cursive (2)
- # data-science (3)
- # datomic (30)
- # devops (1)
- # emacs (13)
- # events (5)
- # fulcro (15)
- # graalvm (3)
- # gratitude (1)
- # humbleui (11)
- # lsp (5)
- # nbb (24)
- # off-topic (11)
- # pedestal (5)
- # releases (1)
- # remote-jobs (1)
- # sci (15)
- # scittle (16)
- # shadow-cljs (15)
- # sql (11)
- # tools-deps (9)
- # xtdb (5)
Hi all. I was trying to wrap my head using promesa
flavoured promises to represent something of a standard but contrived failover example. Apologies for the zoo and lions, but it's more memorable than db calls. It's not obvious from the User Guide how to structure something like this. I thought it would be good to post it publicly for comment, to help anyone in the same bind, and have for future reference.
More in 🧵
The use case is: - Visit the zoo api call - Check all cat cages are closed (a series of async calls) - If lion cage open call lion tamer (async) - propagate error up to visit zoo call and return zoo closed for maintenance, logging appropriately. https://github.com/dmg46664/problems/blob/main/04_promesa_error_handling/promesa_zoo.cljs
@U3BALC2HH No problem! I'm also looking for constructive criticism if any :-)
Ok. Well for starters, is this server/nodejs code or client code?
I'm just trying to wrap my head around the http-style-response maps
The reason is because the calling context is a bit unclear to me -- unless you are plugging this into a framework that is expecting this sort of arrangement, the data returned from the p/resolved
is effectively a wasted no-op. (If this is server code, ignore the following) You don't seem (I could be wrong) to have an async escape hatch. Generally speaking, I tend to expect to see a series of async calls end with some side-effect performed, typically one that updates one or more parts of the application state or triggers an observer or pub/sub chain that results in a different view rendered for the user. I don't see that here. As a consequence of this, I do not think that the error propagation you are looking for is going to propagate in the way you are looking for it to propagate. Typically in a client-side application I would expect an error to result in changing some part of local state to indicate an error occurred, and in changing the state (assuming this were a React flavored app) the view would render the error to the user. I'm know this is about lions and tigers but I'm not seeing any pseudocode to that effect -- so while I think your code is valid, without that side-effect/state-mutation escape hatch, I don't think this is going to serve to propagate the error state in the way you're going for.
Apologies, some context: This code is meant to help me reason about http/lambda aws
calls, spawning dynamodb
promises. Dynamodb gives back http status responses, or errors (not sure of the mix). However, the layered example is generic and so could happen both on the client or server. The http status results simply indicate where this started from. Hope that helps. I just saw your long response now, so will digest it later in the day.
Whoops, just saw the nbb
reference at the top -- my comments may be totally invalid. Since this was in #C03S1L9DN I assumed it was for a client app, my bad, probably
No problem. It wasn't obvious to me that I could say throw errors in p/catch
and that is the way to bubble up the problem. Hence I needed to flesh an example out to have a mind model going forward.
Promesa follows more or less the same model as JS promises, where errors bypass anything later in the chain, until somebody catches it. The catch can either return a value, which will proceed to the next item in the chain or re-throw, which again bypasses anything that fails to catch.
(-> (p/rejected (ex-info "error" {}))
(p/then (fn [_] "I will never be called"))
(p/catch (fn [error]
(print (ex-message error))
(throw error)))
(p/then (fn [_] "I will never be called"))
(p/then (fn [_] "I will never be called"))
(p/catch (fn [error]
(str "resolved: " (ex-message error))))
(p/then print))
error
resolved: error
#<Promise[~]>
Nice. And am I right that unless caught, the error won’t percolate to the main thread, right?
I've only used Promesa a little. But so far, I see no sign of exceptions escaping the promise context. E.g. If you remove everything after the throw
in my example above, nothing blows up.
That part is quite different from using native Promises in JS, where Errors/rejections bubble out the end as Uncaught (in promise)
.
So now that I've learned more today, the alternative feedback I was searching here was https://www.youtube.com/watch?v=bDN898hu_wQ
Hi all, I'm trying to put a function invocation into a map for later execution in clojurescript. So something like:
({:id :task :eval '(println "test")}
In Clojure I could eval the value of :eval later, is there some way to do this in Clojurescript too?Good question. Yes, possibly.
With some caveats and bookkeeping
Yea, but I'm looking to run this in the browser.
That’s the best part
it works in the browser
Even with :advanced compilation
Wow indeed. 😮
So this works for anything that can be run by sci, correct?
Not for functions for the surrounding js namespace.
If you don't really need to serialize it, then just store a function as a function:
{:id :task, :eval #(println "test")}
If you do need to serialize it, then there are two approaches:
• Come up with a fixed registry of functions, assign each function an ID, and use that ID instead of the function - you'd have to make a run-time lookup in that registry
• Use some soft of self-hosting - either with CLJS itself or with the aforementioned sci
, or maybe with something elseYes, I'm going with the first approach as I write this. I even called it "registry" too.
You can load in functions from the surrounding namespace
Just need to specify the execution context
Can also whitelist/blacklist functions
It’s pretty sweet
Wow.. but it can not execute anything JavaScript, right? But still, super impressive, I already have a use case for that!
Can totally execute javascript if you want 😅
The only thing it CAN'T do super well is use macros defined OUTSIDE of sci
there are workarounds but loading entire libraries is not always possible
notably, that makes core.async difficult. However, there are a lot of workarounds for that
Wow, I didn't know sci was that cool tbh. I think I have a great use case for it in mind!
Does anyone have any thoughts on best practices for making an open source clojurescript library designed to wrap an existing javascript npm library? Specifically, I don't like the idea of bundling the npm dependency with the library itself -- or perhaps it could be, but only as a feature flag. It could introduce quite a lot of bloat and prevent folks from using their preferred tools. However, it's unclear to me what best practices would be, syntactically and structurally, for referring to a user provided external dependency. For instance: One option:
(ns something.core
(:require mynpmdep))
Another option:
(ns something.core)
(def mynpmdep js/mynpmdep)
Another option:
(ns something.core
(:require [mynpmdep.core :refer [mynpmdep]))
;; `mynpmdep` is the actual javascript object
Any thoughts?Talking to myself here, #1 seems the cleanest, but that would require users to understand, effectively, how to package a cljsjs dependency unless there's already an existing cljsjs package. :thinking_face:
Two things: • Just don't use cljsjs, don't rely on it - it's pretty much a thing of the past with the current tools, at least the way I see it • This particular problem is discussed every 2-3 months, should be easy to find descriptions of solutions The most reasonable that I remember and gravitate towards myself is just specifying in the README how your library should be used, including telling the user to install a specific NPM package.
Interesting. :thinking_face: I'll do some searches
Yea, would recommend just specifying in the README which packages to install from npm. I do that for two styling libs: https://github.com/dvingo/cljs-styled-components https://github.com/dvingo/cljs-emotion
in the library code you just include it from npm ["@emotion/styled" :as styled*]
if a user goes to use the lib they will get a compile error if the library isn't present
But that also means then that user needs to compile it and can't have it be loaded via script tag, yes?
Part of what's frustrating to me is I'm getting super annoyed at the gigantic size of these clojurescript builds
Including it via a script tag has its downsides. And to deal with a build size, you should be using module splitting.
Agreed, but AFAIK module splitting doesn't do much to deal with the npm deps, which is where the bulk of the size is
If you load your NPM deps via a script tag, you will be loading the same exact total size.
Well, not exact exact, but, as the docs say, "comparable (or often better)": https://shadow-cljs.github.io/docs/UsersGuide.html#js-provider
Agreed -- but there are options for splitting up the npm deps bundle and loading the relevant javascript on demand if you are willing to forgo baking them in with your clojurescript
There's no reason to burden users with all that unnecessary code over the wire before the page even loads
> there are options for splitting up the npm deps bundle and loading the relevant javascript on demand That's exactly what module splitting does in CLJS.
I'm not talking about namespaces here, I'm talking about modules - a completely different thing.
No, I know what you're saying. What I'm saying is that the clojurescript code takes up a fraction of the bundle size -- the npm deps take up the majority. Code splitting the clojurescript is also a good idea but it doesn't do anything to reduce the size of the npm-bundle. Even when you module split, all the npm deps go in the cljs_base.js
artifact. And there's no way I know of to subdivide the npm deps out of the cljs_base.js
artifact. So the result is that the majority (over 95%, in my case) of the size still lives in the cljs_base.js
artifact, even if I'm careful to split out as much as possible into modules.
You can code split out the npm deps, but you still have to bake them all in during compilation, so there's no point in splitting out npm deps.
(I would love to be wrong about this, btw)
Why would an NPM dep go to the base artifact if only one of the CLJS modules uses that dep? I just tried it - split a test app into 3 modules: base, main, ui. Only the ui module depends on React. And in the end, only the ui module contains it - the base one doesn't contain it.
only the UI module contains the React code?
How did you verify that?
interesting :thinking_face:
The trick is that only the ui module should depend on React. If some other module depends on it as well, React will be put into the base module.
Just as per the docs:
> The :base
module declared an empty :entries []
vector which is a convenience to say that it should extract all the namespaces that both of the other modules share (eg. cljs.core
in this case).
Interesting. So that should in turn apply if I'm using reagent? So for instance, if you were to have your ui
module depend on Reagent, and Reagent depends on React, React should still only show up in the UI code?
So if you have e.g. modules A, B, C, and base, where only A and B depend upon some NPM library N, then don't just require it there - instead, add a third module, AB-common, and make it an intermediate dependency of A and B.
> [...] React should still only show up in the UI code? Yes. That's exactly what happened in my case - I used React via Reagent.
As soon as I added "react"
import to the main module, React has moved to the base module. But Reagent has stayed in the ui module.
word. Hmm.
I wonder if I was just code splitting wrong when I tested this or if I finally found the one thing that I can't get figwheel to do properly that shadow is doing properly
In other words, dependencies move to the closest common module. If only one module depends on something - that dependency will be in that module. If two modules depend on something - it will be in the closest "parent" of the two.
I've managed to avoid switching tooling for like 5 years
Can't answer to that question, I haven't used Figwheel for around the same 5 years or so.
Ok so I just want to verify
you had a big index.bundle.js
and then you module split as per the docs
and the compiler intelligently moved the code to the correct places
or did you do your npm deps some other way
did you use webpack to make the npm deps?
NPM deps are used in the most regular way you'd use them with shadow-cljs. I've never used webpack in my whole career. :)
Just follow the module splitting guide in the shadow-cljs documentation. It just works.
I think that's the major difference
Also, shadow-cljs has build reports. On this pic you can clearly see what goes where.
I was thinking it would be virtually impossible to split up a webpack bundle, my brain was doing summersaults thinking about how one would parse index.bundle.js
and assign dependencies to clojurescript dependencies
so that means that shadow is integrating the npm build step instead of webpack
fascinating
So I was unable to replicate those results
Also I'm noticing in the build report you displayed, react
is going in the :base
:
@U05224H0W can you possibly weigh in on this?
https://code.thheller.com/blog/shadow-cljs/2019/03/03/code-splitting-clojurescript.html in case you missed that
Ah, ok, I was looking that this https://shadow-cljs.github.io/docs/UsersGuide.html#_module_splitting I'll check that out
ok, that did. I was able to replicate the result. Dang, thanks. You saved me tons of work.
You too @U2FRKM4TW
Ah, I missed that module splitting is a bit different from code splitting. But the module splitting guide worked for me just as well.
Is there a way of getting a stack trace with source mapping?
(try (throw (js/Error. "Dummy"))
(catch js/Error e
(js/console.error e)
(tap> (.split (.-stack e) "\n"))))
If I do that I see that the console adds the mappings, but my tap contains just the js stack trace. Is there a way of using the source mapping functionality from the runtime?You'd have to use some third-party package. A quick search shows https://github.com/mozilla/source-map/ and https://github.com/JaneaSystems/convert-sourcemap-stacktrace but there are probably others.
I see, but it will require the cljs code to know the location of a source map also
it would be nice if the browser provided that functionality, I mean, it can automatically do it for the console.error
yeah. the stack traces in cljs are slightly better than "segfault, core dumped"... but not much
especially when using core.async 😓
but I do love me some core.async
Don’t catch the error (or throw it again) and you’ll be able to see the stack trace in the browser debugger with source map
@U01V2D5ALKX I can already see the error stacktrace source mapped on the browser console. What I'm trying to accomplish is to obtain it as data
Do you control the error @U0739PUFQ? Could you throw ex-info
instead of js/Error
? much easier to capture the error data that way
@U3BALC2HH what is the difference? What I'm trying to do with that piece of code is just to get a stack trace as data, the throw is catched immediately on the wrapping expression
Ok my bad, I didn't know wanted the stack trace specifically, I thought you just wanted data X_d from the exception, in which case you could (throw (ex-info some-message some-hashmap))
and then
(catch :default e
(do-something-with (ex-data e)))
yeah, my question was how to obtain the current stack trace source mapped
Sorry I misread your question. I think this is not a clojure issue, and it seems the only solution is to use libraries, there's no native API for that. I think https://github.com/stacktracejs/stacktrace.js would fit your needs, but there's others.
hey np yeah, but that requires to create a request and hit some service exposing the source mapping, I was just asking if the browser provided that info some how, given that it is already using it for the console.log
This is directly the browser that overrides JS outputs, the JS engine doesn't have the source map. The browser do itself the required source maps requests. Maybe with inline source mapping you can avoid additional requests.
I mean the browser don't magically get the source map, if there are external source maps it also "requires to create a request and hit some service exposing the source mapping"
If this is an internal tool, it might be possible to use a browser extension to capture this, since it gives more access to browser facilities outside normal js facilities
oh nice! thanks @U01V2D5ALKX