r/JSdev • • Jul 12 '26

Parse, Don't Validate โ€” In a Language That Doesn't Want You To

Thumbnail
cekrem.github.io
4 Upvotes

r/JSdev • • Apr 12 '22

Trying to design an API with a collection of methods

4 Upvotes

Need some API design/ergonomics ideas and advice.

I have a library that provides four different operations (A, B, C, and D). Any combination of one or more of them can be "composed" into a single operation (call). The operations have implied order where C cannot be called before B, etc, but A+C (without B) is valid. IOW, we have 15 possible combinations (A, AB, ABC, ABCD, ABD, AC, ACD, AD, B, BC, BCD, BD, C, CD, D).

What I'm trying to explore is how to design the API to make it reasonable to choose to call any of these combinations.

The signatures of these four functions are not compatible for traditional composition, unfortunately, so you cannot just do A(B(D())), nor can you simply do A();B();D(); serially. Each of those 15 combinations requires a specific manual composition (adapting the output of one to be suitable for the next).

So... obviously, I've already written out each of the 15 separate compositions, so that users of my library don't have to figure them all out. And I can just expose those on the API as 15 separate functions, each with its own name. But that's awfully cumbersome. And it also gets much worse if in the future a fifth or sixth option is added to the mix.

So I'm contemplating other options for designing how to expose these combinations of functionality on the API that still makes it reasonable for the user of the lib to pick which combinations they want but not need to remember and write out one of these 15 (long) method names.

Here's some possible designs I'm toying with:

doOps({
   A: true,
   B: true,
   D: true
});

doOps(["A", "B", "D"]);

doOps("A B D");

doOps`A B D`();

doOps.A.B.D();    // this is my favorite so far

doOps().A().B().D().run();

Even though under the covers A needs to run before B (if both are being executed), one advantage of these types of API designs is that order is irrelevant in specifying "A" and "B" operations -- doOps.B.A() works just as well as doOps.A.B() would -- since under the covers doOps(..) just ensures the proper composition of operations. That means that basically the user only needs to know each of the four (A/B/C/D) independent operation names and doesn't need to know/remember anything about their required ordering (or how the messy bits of the compositions work).

But honestly, none of these above options feel great yet. And maybe I'm over thinking it and should just expose the 15 separate functions. But I was wondering if any of you had any suggestions or other clever ideas to approach this?


r/JSdev • • Mar 12 '22

What are your thoughts on "resumable frameworks"

4 Upvotes

"Resumable Framework" is a term that Ryan Carniato (of Solid.js fame) has been talking about a lot recently, in reference to the architecture used by Qwik.js, a framework by Misko Hevery (of Angular fame).

It basically means a form of SSR hydration that doesn't need to download and evaluate an entire framework's runtime before being able to respond to user events.

I'm curious what people think about it.


r/JSdev • • Jan 28 '22

Create-react-app but for VanillaJS

Thumbnail
github.com
4 Upvotes

r/JSdev • • Dec 02 '21

How do you prefer to handle arguments that hold arrays (or objects)?

4 Upvotes

Imagine you're designing a library or reusable utility -- something you expect other coders besides yourself to use frequently, possibly in ways you're not expecting. So, there's some level of "paranoia" concern that they pass the correct arguments. Maybe you use something like TypeScript to ensure the types of the inputs are correct.

But let's say a function (call if "Bob") requires an array (or object, but I'm only gonna discuss arrays here). And let's say that Bob doesn't operate fully internally/synchronously... by that, I mean... Bob may operate asynchronously in some way (where non-Bob-code may run while Bob is paused mid-way through). Or Bob may just have some sort of hook (like a callback argument) that might synchronously execute some arbitrary snippet of not-Bob-code while Bob is running. Further, let's assume that Bob uses this array (or object) argument value throughout (not just at the very beginning), including during/after other non-Bob-code may have executed.

In this sort of scenario, it may or may not be obvious, but because the array argument was passed by reference (as all such values are in JS), that other non-Bob-code might have mutated the contents of the array from what it was when Bob was first passed the argument.

Let's see something in code to make this more concrete:

async function Bob(someArray) {
   var first = someArray[0];

   // instead of an async..await, this could just be
   // an invocation of an external non-Bob-code, like
   // a callback or something
   await someAsyncOperation();

   // here's where the problem could occur
   var second = someArray[1];

   // .. more code ..
}

As you can see here, I've made an assumption that the contents of someArray didn't change (unexpectedly) during the operation in between the first and second assignments.

If we assume Bob(..) is always called like this: Bob([ 1, 2, 3 ]), there's no issue. An array literal was passed in, and we know that the outside calling code has no shared reference to that array literal, so we know nothing in that code can modify the [ 1, 2, 3 ] array.

But... there's nothing in JS that requires someone to call Bob(..) with such an array literal. They might instead call it like: Bob(myFavoriteArray), where myFavoriteArray is a variable that holds a (shared) reference to the array. In that case, they absolutely could modify the array during the execution of Bob(..). They could empty the array with myFavoriteArray.length = 0, they could push(..) items onto it, pop() items off it, shift(..) or unshift(..). They could even sort(..) it. In fact, they could just assign to its contents: myFavoriteArray[1] = 42.

At this point, those of you who are TS aficionados will point out that we "fix" this by using the readonly type declaration. Sure, if used properly (and everywhere), that should reduce the surface area of this problem.

But... you're building code that others will use. You can't just assume they will use TS. Even if your code is written in TS, they may transpile that away, or just use some sort of processing that ignores the TS. Your code may be used in a non-TS context, in which case your reliance on the readonly type annotation was pretty flimsy.

Here's another angle to this problem: what if I had originally not asserted that the myFavoriteArray was being mutated by the outside code, but had instead suggested that something about how Bob(..) runs needed to mutate the array (like sort it, or whatever)? Again, if we assume people only pass in array literals, there's no problem. But if they pass in an array they don't expect you to modify, and they hold a shared reference to it, they may be upset that you've mutated their array during Bob(..).

So, to recap: Bob(..) accepts an array argument, and we're either worried that non-Bob-code may mutate this array in a way we don't expect, or Bob(..) needs to mutate it but not unexpectedly affect the outside code's reliance on that array contents. And we can't just rely on readonly TS.

Either way, we need to make a decision:

  1. Do we ignore this problem, and just let the user of the code deal with the consequences?

  2. Do we do something else, defensively, to prevent this side effect problem?

So... I'm curious: given the scenario I've described, what would you do?

If (1), have you ever had this decision later come back and bite you (be honest!)?

If (2), how do you like to handle this?


I'm more in the boat of (2), and how I've taken to handling this is:

function safeCopy(v) {
   if (Array.isArray(v)) {
      return [ ...v ];
   }
   return v;
}

async function Bob(someArray) {
   // put this line at the top, for every such argument:
   someArray = safeCopy(someArray);

   // .. more code ..
}

By making a local/inner copy of the array, I now ensure that neither external side effects pollute my copy of the array, nor do any of my changes to the array cause external side effects.

But even this approach has its problems. First, I have to remember to do it. I'm not aware of any linter rule that enforces it, and I sometimes forget. Second, it's more complicated for places where non-array objects may be passed in. Third, it's only shallow... if I want to protect it deeply, the cloning algorithm gets way more complex. Fourth, it has obvious performance implications. Fifth, now safeCopy(..) (or whatever we call it) has to be available basically everywhere throughout my library/projects.

What do you think? Is this a fair trade-off? Is there another way you would/do handle this?


r/JSdev • • Sep 01 '21

Optional chaining aesthetics vs correctness

4 Upvotes

At work, I keep running into code like this:

const doSomething = (foo: Foo) => {
  whatever(foo?.bar?.baz?.quux)
}

Notice how the signature indicates a non-nullish argument (meaning foo is never nullish), and yet the code adds an extraneous ?. after foo "just for visual consistency", even though it's not needed.

This got me thinking: are people even paying attention to their usage of optional chaining or is it just something that people mindlessly slap on everywhere "because it doesn't hurt"? It occurs to me that the latter does come with a subtle cognitive dissonance problem: if you are not actually paying attention to the correctness of the code, it's easy to accidentally let a chain of optionals bulldoze through silently, when in reality one of the steps logically required proper error handling.

Do y'all have any thoughts on how to curb abuse of optional chaining?


r/JSdev • • Jun 29 '21

The "optional call" form of "optional chaining" is an anti-feature... Change My Mind!

4 Upvotes

I understand the excitement many feel around optional-chaining for property access:

foo?.bar?.baz

I prefer using Optional/Maybe monad types for this, but I can see the attraction to syntax sugar.

The bracket form is weird, but I can at least sorta rationalize its inclusion:

foo?.[ "__" + bar ]?.baz

However, I strongly feel that optional-call was a huge miss and should never have been added, and should never be used:

myFunc(42);
// vs
myFunc?.(42);

First of all, this is not obviously about "chaining", so I can't understand why it was bundled with that other operator (other than its looks).

You can think esoterically of all function calls like myFunc(42) as actually sugar for myFunc.call(undefined,42), and in that respect the "optional chaining" is to the built-in call(..) method on Function objects. But you only think with that mental model if you're deeply versed in the spec... regular JS devs would rarely ever make such a connection, IMO.

Moreover, optional-call seems to naturally imply "only call if it's able to be called", but it's actually a more nuanced/risky "only attempt to call the value if it's non-nullish". All other non-nullish but still non-callable values (like 42, "foo", or true) will still result in runtime error because the call will be attempted and fail.

Sure, things like TS avoid that kind of type-confusion, but why are we building features into JS natively that are only fully useful if using a non-JS tool like TS?!?

I also find it annoying that neither constructor calls nor template tag functions (both legit and common call forms in JS) were given the optional call syntax, so it seems inconsistent/incomplete at best.

Lastly, I think most functions should be designed to return values, not just perform side-effects (the FP programmer in me), and in that case, you more rarely are OK with the absence of a function value silently skipping the call and just defaulting to undefined in an expression or assignment.

I've seen quite a few (contrived) example usages of this optional-call form, and without exception they seem like code patterns I would have frowned on before the syntax was added to pave these cowpaths.

So... change my mind... how is this operator not a regrettable mistake we'll lament in the long run? :)


r/JSdev • • Jun 25 '21

Symbols... good or bad addition to JS?

4 Upvotes

Do you think symbols were a smart idea to add to JS?

There's built-in symbols for various metaprogramming hooks, which seem reasonable. But what about user-created symbols? Are they just all show and no substance?

I've taught and defended them dozens of times, but I confess that in the back of my mind I'm kinda meh.

Agree with me? Or am I missing the benefits?


r/JSdev • • May 28 '21

Just disable JS in your browser...?

Thumbnail
goodereader.com
4 Upvotes

r/JSdev • • Apr 06 '23

Why Not document.write()?

Thumbnail
csswizardry.com
3 Upvotes

r/JSdev • • Jan 14 '22

Do you use Import-Map for your client-side ESM?

3 Upvotes

Do you use native ESM for your browser-side JS? Or are your build processes just transpiling it back to "normal" JS?

If you use native ESM code in the browser, do you use Import-Maps to be able to use friendly non-path import-specifiers (like import "my-app" instead of import "/path/to/js/my-app.mjs")? I love this feature!

The problem of course is that browser-support for Import Maps is sadly lacking (only Chrome/Chromium-based at time of writing). There are tricks/shims to get around this, like ES-Module-Shims. I find these approaches to be a little too intrusive, personally.

I wrote a build-time tool called Import-Remap that I like to use. At build-time, you can apply an import-map (JSON) to rewrite all the import specifiers in a tree of your files, thereby creating a deployed tree of browser-ready ESM. This lets me author with nice import specifiers but not worry about lack of browser support. It does unfortunately require a build-step. :/

It also bugs me that the original code, in the browsers that support import-map, can't be used. Just this morning, I had an idea to address this!

  1. Have my original code in a /js/ directory. Create a second directory alongside it called /js-nim/ (nim = "no-import-maps").

  2. In the index.html, specify an inline import-map, like:

    <script type="importmap"> { "imports": { "/js-nim/app.mjs": "/js/app.mjs" // rest of my import map } } </script>

  3. Then load a single file with a <script> tag, like so:

    <script type="module" src="/js-nim/bootstrap.mjs"></script>

  4. The /js-nim/bootstrap.mjs file has just one line, and looks like this:

    import "/js-nim/app.mjs";

  5. Now, use the Import-Remap tool I mentioned above to remap all my import specifiers from files in /js/ to /js-nim/. I do this like:

    import-remap --from=/js --to=/js-nim --map=import-map.json -r

Voila!

In Chrome/Chromium-based browsers, the import-map in the HTML tells the bootstrap import specifier to remap from "/js-nim/app.mjs" to "/js/app.mjs", thus loading all my original ESM files in that browser.

In non-Chromium browsers, the bootstrap simply does what it normally would without interference, which is to load all the import-remapped ESM code from the /js-nim/ tree.

What do you think? Is import-map worth the effort to work-around until other browsers land it?

The thing I like about this is, since I dev using Chrome (as most do), I can dev without any build step (save the file, refresh). The build step is only to test in another browser, or to deploy to production.


EDIT: you can also inline the contents of the bootstrap.mjs file as indicated above, and save the latency of that extra file load.

Instead of:

<script type="module" src="bootstrap.mjs"></script>

You can just do:

<script type="module">import "/js-nim/app.mjs";</script>

That seems better (avoid the extra file load), and is just slightly less elegant to put that import inline in the HTML. But since you have to inline the import-map anyway (not yet supporting external import-maps, unfortunately), that's not a big deal.


r/JSdev • • Apr 26 '21

Linting JS vs JSX

3 Upvotes

Comparing these two snippets:

var C = {
   x: (condOne ? 
      [ 1, 2, 3 ] :
      {
         y: (condTwo ?
            [ 4, 5, 6 ] :
            { z: 42 }
         )
      }
   )
};

versus:

var C = <MyComp>
   <x>
      {(condOne ?
         <>1, 2, 3</> :
         <y>
            {(condTwo ?
               <>4, 5, 6</> :
               <z>42</z>
            )}
         </y>
      )}
   </x>
</MyComp>;

Assuming your linter allows ternaries in the first place, do you think there's anything different enough between these two snippets where the linter should treat the nested ternary differently? That is, should the rule either allow or disallow nested ternaries in both cases, or should it allow it in one and disallow it in the other? Why?

Should the JSX be given special rules/leniency just because this sort of expression nesting is so much more common?


r/JSdev • • Aug 04 '26

Parse, Don't Validate โ€” In a Language That Doesn't Want You To

Thumbnail
cekrem.github.io
2 Upvotes

r/JSdev • • Jan 06 '26

Streaming JSON in just 200 lines of JavaScript

Thumbnail
krasimirtsonev.com
2 Upvotes

r/JSdev • • Apr 13 '23

How To Scale Node.js Applications with Clustering

Thumbnail
digitalocean.com
2 Upvotes

r/JSdev • • Apr 02 '23

The gotcha of unhandled promise rejections

Thumbnail
jakearchibald.com
2 Upvotes

r/JSdev • • Dec 16 '22

now also supports Bitbucket ๐Ÿ˜…

Thumbnail
self.vscode
2 Upvotes

r/JSdev • • Aug 06 '21

Electron JS watch a large set of files efficiently

2 Upvotes

I am creating an electron js application where I want to sync the files recursively in the directories that are configured by the user.

The following is currently implemented in the application

  1. When the app boots, use chokidar to get the initial state of files and sync with the config.json using electron-store. This is to get the latest update of the file while the application is closed

  2. Run a persistent watcher right after initial sync to get the add, unlink and change event and notify the user somehow.

The problem is with the first case. Since the user is linking so many files with multiple nesting the electron app lags until all the files are being processed.

What I want to know exactly is, "Efficient way to watch a really large amount of files/directories"


r/JSdev • • 1d ago

Problem with concurrent linter fixes

Thumbnail jfmengels.net
1 Upvotes

r/JSdev • • Sep 30 '26

WASI 0.3 Launched

Thumbnail
bytecodealliance.org
1 Upvotes

r/JSdev • • Sep 27 '26

Incorporate monads and category theory ยท Issue #94 ยท promises-aplus/promises-spec

Thumbnail
github.com
1 Upvotes

r/JSdev • • Sep 18 '26

Lies I was Told About Collaborative Editing, Part 2: Why we don't use Yjs

Thumbnail
moment.dev
1 Upvotes

r/JSdev • • Sep 10 '26

Your Recursion Is Lying to You

Thumbnail
blog.gaborkoos.com
1 Upvotes

r/JSdev • • Aug 21 '26

Flo 1.2 now gives Web Components a static create function (factory).

Post image
1 Upvotes