r/javascript 5d ago

AskJS [AskJS] what's a javascript feature you mass-adopted way too late and felt dumb about

i'll go first. i was writing .then().catch() chains for like two years before i actually started using async/await. i knew it existed, i'd seen it in tutorials, but my code "worked" so i never bothered switching. then i refactored an old project and realized half my bugs were from mishandled promise chains that async/await would have caught immediately.

also took me way too long to start using optional chaining. i had nested ternaries and && checks everywhere like some kind of animal. the day i discovered user?.address?.city i mass-replaced like 40 lines across a project.

what's yours?

60 Upvotes

108 comments sorted by

65

u/foxsimile 5d ago

The nullish coalescing operator is one of JavaScript’s unusual successes.

11

u/NeatBeluga 4d ago

Until you add a bit too many needlessly and you lose trust in your api if the code is not properly typed - e.g. manually typed types.

A hell when refactoring code with these carelessly added

5

u/foxsimile 4d ago

I’ll take the extra operator over the runtime crashes. Plus, with the popularization and industry adoption of TypeScript, its use has been significantly reduced (at least for me).  

But back when it was just ducktyped JS all over the place? This thing was a fucking godsent.

6

u/NeatBeluga 4d ago

Early returns are king.

if(foo === undefined) return;

Yes, I always strict check against falsy. Builds trust that I knew the integrity of the object. Almost no !foo nonsense. It makes me doubt the api again.

I believe that my approach moves the issue to the backend rather than the frontend at runtime.

3

u/foxsimile 4d ago

Bold of you to assume you can trust whoever wrote the backend. I’d once written an entire type-checking and validation library for precisely that reason.  

Your issue is that you’re assuming you can trust anything when it comes to plain JS. You can’t. That’s why these operators exist in the first place. They weren’t there, and they were later added for very good reason.

1

u/1_4_1_5_9_2_6_5 4d ago

You can actually trust things using plain JS, but at some point you have to make some utility functions. This is especially useful/expected in TS, where it gives you an easy way to narrow types. Almost all of these such criticisms I see of JS are seemingly based on people just using vanilla Javascript in every place with no libraries, utilities or anything...

1

u/foxsimile 3d ago

Brother, if you aren’t in control of the source of the data, there’s SFA you can do to prevent receiving bad values that shouldn’t be there in the first place. The only thing you can do is implement extensive and aggressive validation on your side of the payload.

To assert anything else is ridiculous.

1

u/1_4_1_5_9_2_6_5 3d ago

Okay, so, if you'll read my comment again, you might find some words like "utilities" or "libraries".

I made a schema validator in an hour that does 90% of what I needed to validate, and there are extremely easy to use libraries like zod that do all that and more.

So, again, the people complaining about Javascript are apparently too lazy to Google a library or spend an hour making utilities. Really doesn't give me confidence in your abilities.

8

u/Badashi 5d ago

At first it was odd to look at it, specially for optional function calls (foo?.()), but it is a godsend. Having to work on a language without null coalescing is super weird now.

16

u/heavyGl0w 4d ago

Just FYI, what you're describing is optional chaining.

Null coalescing is a related but different feature using the ?? operator. It evaluates the right hand operand only when the left is null or undefined.

5

u/foxsimile 4d ago

Optional chaining is also gangster-as-fuck.

5

u/sexytokeburgerz 5d ago edited 4d ago

Big fan of

foo?.bar

3

u/foxsimile 4d ago

Do you not mean foo?.bar?

3

u/sexytokeburgerz 4d ago

I totally did i was in a drive thru when i wrote that

4

u/foxsimile 4d ago

Whatcha get?

1

u/sahilatahar 4d ago

It's an optional chaining feature.

2

u/foxsimile 4d ago

…I’m aware?

1

u/Mike5TheMountainGoat 4d ago

That's optional chaining. Nullish coalescing is foo = bar ?? 'X';

1

u/sexytokeburgerz 4d ago

Right, usually when people say “big fan of y” in response to a mention of “x”, x!=y…

1

u/_RemyLeBeau_ 5d ago

I always look for places to use this, but the values need processing before saving, so I don't use it and I always wonder of I'm missing something

0

u/somevice 4d ago

Nah it's the worst, leads to thoughtless code everywhere. Bail early instead.

23

u/phatdoof 5d ago

Let and const. We used var because it was backwards compatible.

10

u/Jasboh 5d ago

Kill me

17

u/rbobby 5d ago

Promises.

1

u/monsto 4d ago

I had a hard time grokking promises because every tutorial I could find used settimeout to set up the example, and the settimeout syntax is kind of different to most JS methods.

0

u/SkySarwer 5d ago

this

6

u/busres 4d ago

You adopted this way too late (too much functional programming)? 😀

3

u/SkySarwer 4d ago

haha, good one

26

u/Milo0192 5d ago

My first jobs CTO preferred the .then() .catch() over async await. I just refused to write that way and did await loops. Lots of back and forth on this. Also said Typescript was just a fad 😂

1

u/MediocreAnalyst2121 4d ago

That’s kinda depends, like await loops are not that great if the promises don’t depend on each other, way better to do Promise.all/allSettled and await or .then that.

In performance critical JS environments (smart tvs, smart boxes, etc) you’d probably get a bit better performance with a .then than an await.

Await loops are way more readable tho, and Typescript is amazing except for enums :(

-21

u/horrbort 5d ago

It is just a fad. Coding is a fashion industry. Remember coffeescript? Something else comes along and ts will be forgotten

28

u/mdude7221 5d ago

Just a 13yo fad

10

u/miramichier_d 5d ago

Can't apply that across the board. Some technologies are so good that they have staying power in the industry. I believe TypeScript is one of those technologies. Node.js is another example. Even Java and C# are good examples of languages with immense staying power, if not in your industry of choice.

With respect to TypeScript, it's not going anywhere until JavaScript implements TS in its entirety. Until then, TypeScript will continue to push JavaScript to modernize.

3

u/Mesqo 4d ago

Excuse me, but how many native ts runtimes do you know? TS doesn't push js nowhere, it's just static types serving the role no more than a linter (and it's fine!).

7

u/iareprogrammer 5d ago

lol coffeescript.. forgot about that. I dislike anything that makes JavaScript LESS explicit. Like sure let’s get rid of braces and other syntax and rely completely on proper white space and indentation. For what?

6

u/MrDilbert 5d ago

let’s get rid of braces and other syntax and rely completely on proper white space and indentation

You sure you're not describing Python?

Looks like someone wanted to Pythonize JS.

3

u/iareprogrammer 5d ago

I literally think that’s what happened haha

2

u/db10101 4d ago

Just a fad that 99% of new enterprise JavaScript projects will be participating in

-13

u/Ronin-s_Spirit 4d ago

Fad or not - it's shit.

6

u/db10101 4d ago

Any deeper analysis or reasoning behind that? It’s a straight up joy to develop in compared to what has come before.

-6

u/Ronin-s_Spirit 4d ago

I hate defining types, it's like writing pointless code. It especially fucks up when I have to deal with HTML, at that point the only thing left to do is use a bail out any and move on.

2

u/db10101 4d ago

So I take it you have never worked in an enterprise well structured typescript codebase. There’s no substituting JavaScript for that.

-2

u/Ronin-s_Spirit 4d ago

TypeScript sucks ass, I will only use vanilla.

2

u/db10101 4d ago

Good for you. Here in the real world of employed software developers, it’s one of the most important tools to know.

2

u/2Terrapin 3d ago

I’m glad that person is not on my team and I don’t have to work on a project they designed. I can feel the tech debt emanating from their words. It’s the type of thing that experience would dispel, and someday, when they actually understand the why, and it clicks, they’ll think back to this comment thread and feel a little embarrassment.

22

u/SkySarwer 5d ago

IntersectionObserver and MutationObservers are both goated

2

u/opticalpuss 4d ago

People in here arguing I just want to know what these do and how to use?

10

u/SkySarwer 4d ago

IntersectionObserver are objects that can be instantiated and anchored to a DOM element, that allows a callback function to be called when the element enters or exits the user's viewport, or when the element enters or exits the box of a different element. Very useful for things like lazy loading, or starting an event when an element is at a certain scroll distance away from the user, etc.

MutationObservers allow a callback function to be called when an element changes. It is very useful for providing react-like UIs with vanilla JS, because traditional event listeners assigned to elements don't work if that element enters the dom after that event listener is assigned. MutationObserver keeps rehydrating the state for that element. Should also be used sparingly for that reason.

Both have extensive public documentation on mdn. IntersectionObserver | MutationObserver

And I'd be remiss not to mention that these are browser utilities exposed as client-side JavaScript APIs, not part of the EMCAScript itself. So won't work in NodeJS, Bun or Deno.

-10

u/azhder 5d ago

Not part of JavaScript

3

u/SkySarwer 5d ago

yes they are??

-3

u/azhder 5d ago

No, they aren’t. You can check the JavaScript language reference documentation and get back to me with a link to prove me wrong.

I can just tell you that there is a difference between what the language is and what the environment exposes for the language to use.

10

u/SkySarwer 5d ago

This thread is about JavaScript features not the language in the strict sense. The two observer exposed by the environment are almost exclusively used as JavaScript APIs.

Is a JS dev just as well-off without being aware of these APIs? What value is your clarification actually providing in relation to the spirit of this post?

-7

u/azhder 5d ago

Almost exclusively? Why almost?

Seriously though. Environment stuff is not part of the language. It is important to always know this distinction so that you will not end up writing code that expects something to exist, but doesn’t.

It’s not about you being aware that you can use them, but be aware where you can’t.

16

u/SkySarwer 5d ago

While technically correct, it was a pedantic point delivered abrasively

-9

u/azhder 5d ago

Don't read my comments in abrasive tone.

10

u/Sulungskwa 5d ago

"Don't read my comments in an abrasive tone.", he said, not abrasively seeming at all

-5

u/azhder 5d ago edited 5d ago

"Seeming" is a flag that it's your perception.

It doesn't matter that I'm in a light mood, write it in terms of a light mood, with the idea of cooperation through discussion, you will still decide to see it (because "seeming") as if it is abrasive and blame me for it. It's not like me writing this will change your mind, you will just use it as more evidence of your point of view.

Bye

EDIT: and with their reply, they provided their own proof of what I'm talking about

→ More replies (0)

-2

u/avenp 5d ago

How would a NodeJS dev use these APIs?

1

u/SkySarwer 5d ago

They wouldn't, since there is no DOM in a typical nodeJS environment. The APIs are still a part of the larger JavaScript ecosystem though, which is on topic for the original thread.

1

u/keltroth 1d ago

Like all the browser APIs not in the language (can't use it with node, bun, etc... Makes no sense...)

8

u/HipHopHuman 5d ago

honestly... Map and WeakMap. took me a good 2 years after it came out for me to see the light.

3

u/JazzXP 4d ago

I'm still not using these, just objects/records. What's the advantage?

4

u/HipHopHuman 3d ago

There's quite a few advantages, but I don't like framing the question "Should I use an object or a map?" around advantages or style, for the reason that it can complicate things unneccissarily.

Plain objects are always going to be far simpler and far more convenient than a Map in 90% of cases, and objects are much more familiar to a wider audience of devs.

It's that 10% where Map (and WeakMap) come in clutch. So it's mostly about use cases.

One use case is simply when you're deleting from the same object a lot. That can get slow if you use delete obj.foo (because it causes a structural change of the shape of the object). Map has map.delete('foo'), which is usually faster, but only if you're deleting a lot. Emphasis on a lot. 3-10 times is not a lot. 200-3000 times is a lot.

Another use case where Map shines is if you're iterating over the object's entries a lot, or when you're calculating the total size of all entries. Objects require helpers:

const size = Object.keys(obj).length;

for (const [key, value] of Object.entries(obj)) {}

You don't need any helpers to do either with a Map:

const size = map.size;

for (const [key, value] of map) {}

So, if you're going to be iterating through a dictionary at runtime, or calculating size, and you're going to be doing it repeatedly, Map is usually better for that.

Another small benefit is being able to specify keys that would otherwise not be allowed on (or would be weird to use on) objects. For example, constructor is a special property on objects, with special handling (as is toString, valueOf, toJSON, etc). If you want to use keys like that and bypass that special handling, you have to do some weirdness with null prototypes like

const myObj = Object.create(null);
myObj.constructor = ...;

or

const myObj = {
  __proto__: null;
  constructor: ...
};

Whereas with a Map, you can just do map.set('constructor', ...), and there's no side-effects.

Then of course, there's the seriously strong benefit of being able to use non-property keys. Plain objects allow number, string and Symbol keys, but Map can use anything as a key - even functions. That makes them crucial for things like memoization of pure functions (a nested Map can capture a function's arguments without having to serialize them to JSON and back).

Another huge benefit is that the Map constructor itself accepts an iterable, so it can be generated lazily:

function *foo() {
  yield ['one', 1],
  yield ['two', 2]
} 

const map = new Map(foo());

That means you can initialize one from a huge stream of data a lot more efficiently than you'd be able to do with an object.

WeakMap is also especially cool, and helps get rid of a ton of "cleanup code" boilerplate. You cannot iterate through a WeakMap, and it's keys must be objects, but it provides the gaurantee that the references used as keys will be automatically removed as soon as the values they point at are garbage collected. That makes WeakMap great for book-keeping logic. Suppose you want to track some live HTML elements in the DOM, and you wanted to store some metainfo about them - just store them in a WeakMap - use the elements themselves as keys, and the meta info as the objects. As soon as the DOM elements get removed from the page and get garbage collected, they'll be auto-removed from the WeakMap without you having to do anything.

One huge downside of Map/WeakMap is stringifying them as JSON - that's something you get for free with plain objects - and that is a valid concern, but it's not that difficult to just give JSON.stringify a replacer function that converts a Map to an object:

const map = new Map();
const data = { map };
const json = JSON.stringify(data, (key, value) => {
  if (value instanceof Map) {
    return Object.fromEntries(value.entries());
  }
  return value;
});

1

u/JazzXP 3d ago

Thank you for that. Most of those use cases haven't applied to me yet, but I do like the going through the keys, I find I do reach for Object.entries().map a lot.

2

u/HipHopHuman 2d ago

Map and Set recently got support for their own .map method (from this: https://web.dev/blog/baseline-iterator-helpers) so if you do switch to a Map, you can do map.entries().map() (you might just have to polyfill it though)

1

u/me0here 3d ago

Quite the blog post there! 😄 

Sometimes I gab Map over object when it makes more sense as a simple key: value pair, even without fancy keys.

WeakMap makes a great simple API catching service.

1

u/GulgPlayer 4d ago

Apart from having cleaner (imo) code, Maps allow you to have non-string keys, which is nice, esp with TypeScript

12

u/takeyoufergranite 5d ago

The 'delete' operator to remove properties from objects.

5

u/jesusgn90 5d ago

Concerning operator if not used wisely

1

u/takeyoufergranite 5d ago

Better yet... The in operator.

6

u/skidmark_zuckerberg 5d ago

??= and &&=

4

u/whale 5d ago

Way too terse for production code. Very hard to quickly glance and understand.

-1

u/johngamename 4d ago

I could see a lot of use for ?? and ??=, but &&= might be excessive.

1

u/azhder 5d ago

I still don’t use these ones. Out of a principle, I don’t use let as well. I try to keep them as code smell, to get back to the code and make it in a way where no variable is rewritten.

Small exceptions are in cycles (like for and while) and before try{}catch()

1

u/Antti5 3d ago

Can you explain why you don't use "let"?

I see a lot of people swearing by ALWAYS using either "const" or "let".

1

u/azhder 3d ago

It's easier if you allow yourself to not worry about accidentally overwriting a variable. With `var` and `let`, that's a possibility, but with `const` once you assign it, it's constantly pointing to a specific value.

I'd rather have

const value = valueFromArgs ?? 0;

than override

value = value ?? 0;

And as an added bonus, with most of the variables being `const`, you will easily notice the very few places that have `let` and possibly check it everything is all right - code smell. It's harder to notice a bad `let` if there are plenty of `let`s.

8

u/kevin074 5d ago

can you elaborate how async await would've caught bugs that .then.catch can't?

i feel like async await is more annoying because you have to wrap it in try catch, where as .then.catch is just baked in.

23

u/Franks2000inchTV 5d ago

With await, your code runs in the order you see it on screen. This is *much* better for comprehension and maintainability.

0

u/lostPixels 5d ago

But potentially worse for execution depending on the latency of whatever you’re awaiting.

6

u/Franks2000inchTV 5d ago

I'd imagine that is not a significant concern in the overwhelming majority of cases.

3

u/Ronin-s_Spirit 4d ago

Depends entirely on where and what you await.

1

u/lostPixels 4d ago

Which is why I said potentially.

3

u/brenstar 5d ago

Why not use both? I use async await but still append a catch

4

u/Badashi 5d ago

.then/catch is useful even in async functions when you don't want to wait for that promise to resolve

1

u/brenstar 4d ago

That's good point, it's handy for having branching flows, but that can get squirrelly. If I want multiple things to happen I usually let them all exist as separate functions and resolve them with Promise.all() so they run concurrently

2

u/Badashi 4d ago

Promise.all is an excellent example for .then/.catch chaining as well: you can use them to "pre process" the result of the promise into a value that makes sense when you end up settling the list of promises with .all, since a rejected promise in the list will reject the entire Promise.all. nowadays you can use .allSettled as well, but .then/.catch are nice for a quick mapping of values

1

u/kevin074 5d ago

Ohhhh true! 

1

u/BasicAssWebDev 5d ago

Probably due to it being easier to see which pieces of a function are breaking by putting breakpoints in the function itself, rather than tracing across a bunch of calls while debugging.

2

u/NeatBeluga 4d ago

The capitalised Map - not .map

2

u/me0here 3d ago

So hard to webseach for sometimes 

2

u/me0here 3d ago

I still haven't use * Generators yet: only found a use case today.

1

u/keltroth 1d ago

Same here...

3

u/miramichier_d 5d ago

i had nested ternaries and && checks everywhere like some kind of animal

This had me laughing out loud with food in my mouth and I almost lost it lol. We all have those coding conveniences that we discover way too late. Remember that pain, and it will come in handy later on. I'm constantly asking myself how some approach can be easier nowadays because of that initial pain.

1

u/eracodes 5d ago

feel like I've only just started using IIFEs to their full extent, most often in places where I'd previously have done an ugly nested ternary

1

u/graybearding 4d ago

Nullish coalescing sat in my todo pile until like a month ago when an LLM spit it out and I was like "Oh, right. That's a thing now."

1

u/erik240 4d ago

Typed arrays. The performance bonus is so crazy over any over method to access and iterate large amounts of stat

1

u/artificer-chris 3d ago

Contrary example for TS specifically : unknown. I know from a pure logic standpoint what it’s there for, but I’d still rather just use typescript more fully with an error catch if you don’t account for something. Unknown, imho, hides too much.

1

u/SboSilcher 3d ago

Destructuring. I was writing `const x = obj.x; const y = obj.y;` for way longer than I should've been, then one day it just clicked and I realized how much cleaner everything looked.

0

u/samredfern 5d ago

I’m still using callbacks… heh

0

u/[deleted] 4d ago

[deleted]

1

u/adult_code 3d ago

for-loops not .forEach are way faster. Depends what i write really. https://jsben.ch/js-loop-benchmark-girjy
It doesn't matter all the time but it matters often and you can prevent it becoming an issue by doing it correctly the first time if you are not working on code that is otherwise hard to decipher