9

I'm new to node, but I come from an extensive background in programming. Everywhere I looked (both in tutorials and in production code I've seen around), people overwhelmingly use hard coded strings instead of constants to identify events.

I picked one random example out of npm's most depended upon packages list to illustrate what I mean: the «request» library — why do they emit and consume the «data» event by typing the string 'data' every time, instead of defining a library-wide constant?

Using constants would be considered good practice in any programming language I'm aware of, and yet node developers seem perfectly content with the hard-coded approach. Why?

Bogdan Stăncescu
  • 5,320
  • 3
  • 24
  • 25

2 Answers2

5

Because it doesn't work for Node.js due to the dynamic nature of JavaScript.

Supposed, a module defined a constant data. Then it would need to export this, as part of its event-emitting API. Something such as:

const data = 'data';

class Api extends EventEmitter () {
  constructor () {
    setTimeout(() => {
      this.emit(data);
    }, 2 * 1000);
  }
}

module.exports = {
  data,
  Api
};

Then, from the outside we would have something such as:

const foo = require('./foo');

const { Api, data } = foo;

const api = new Api();

api.on(data, () => {
  // ...
});

Basically, something like this. Now what if you mistyped data in the beginning? What if instead of

const { Api, data } = foo;

you mistyped:

const { Api, Data } = foo;

It would work, without any error. Data would just be undefined. You don't get an error in JavaScript when you try to access an object's property that does not exist, you just get back undefined.

So, you won't get a compiler error, but now you're emitting (under the hood) 'data', but what you're listening for is undefined. The constant does not help in any way, you still have to make sure that you do not mistype its name.

And that is in no way better or worse than using a string where you are not allowed to mistype anything.

So, to cut a long story short, constants don't improve the situation, they just lead to more typing and make things more complicated - but they don't solve any real problem.

Golo Roden
  • 140,679
  • 96
  • 298
  • 425
  • Well, PHP is a weakly typed scripting language as well, and you can use undefined constants and variables as well — and yet, PHP programmers do consider it good practice to define constants for constant stuff. Also, there are tools, such as Visual Studio Code, that do seem able to dig into javascript code and successfully implement autocomplete – which in fact reduces the amount of typing, at the end of the day. Finally, while I understand you only intended to jot down some code so we can have something to talk about, there could be better ways of formalizing this than your code sample. – Bogdan Stăncescu Mar 10 '17 at 09:11
  • You wanted to know why JavaScript / Node.js does it the way it does. That's the reason. Yes, there are tools such as VSCode, but that has nothing to do with the language or runtime itself. You don't want a language / platform to depend on a specific tool from one vendor, do you? So that's the reason. Like it or not, but that's the way it is. – Golo Roden Mar 10 '17 at 18:45
  • 1
    Please remember we're talking about coders' practices, not about language design here. Your original response indicates what I can only surmise are unsourced, personal speculations on why people tend to think strings are just as good as (if not better than) constants. Now you're getting overly defensive and attempt to explain practices (which are by definition coders' decisions) as if they were somehow baked in the language. I'm afraid that's not a line of reasoning I can agree with, like it or not. – Bogdan Stăncescu Mar 10 '17 at 18:53
  • Using ES6 modules instead of destructuring would solve this problem – Bergi May 28 '17 at 12:38
3

The short answer is that strings are the best constants available to JavaScript. Hiding them behind some static library variable does not make developers' code any more terse, readable (my opinion), or performant; this is why it never became popular convention to do so.


Fragility

Your question seemed to imply that hard-coded strings are somehow more "fragile" than using variable constants. However, I would argue that this is only true when the "constant" is expected to change over time, and this is hardly the case for any event type because Node.js libraries (and browser APIs, too) strive to maintain some degree of backwards compatibility between releases. Furthermore, the odds that someone would mistype the variable are the same as when using a hard-coded string, because JavaScript has no compile-time checking for property existence or anything else. No guarantees are made either way.


Extra Context

You might have noticed that JavaScript lacks an enum type, which is annoying in a lot of ways.

Older APIs used integer values to fill this void in the language, similar to the way people used to do it in Java. For example, Node#nodeType and XMLHttpRequest#readyState are both designed to be used like so:

var node = document.createTextNode('')

console.log(node.nodeType) //=> 3
console.log(node.nodeType === Node.TEXT_NODE) //=> true

var xhr = new XMLHttpRequest()
console.log(xhr.readyState) //=> 0
console.log(xhr.readyState === XMLHttpRequest.UNSENT) //=> true

This design pattern came to be seen as archaic over time, because people realized (as they did in Java) how little information a number like 4 gives when printed in an error as opposed to the semantic property name UNSENT.

The best available alternative in JS was, and continues to be, string values. Modern APIs like fetch() make use of them quite frequently for passing meaningful configuration options to each method.

The JS community seems to be leaning towards this approach over time, and I think there are some very compelling reasons to do so. However, when you come from a more traditional programming background (especially a familiarity with strongly-typed languages), it might take a little time for you to feel comfortable with this approach. Just keep an open mind and read as many books on the language as you can.

gyre
  • 16,369
  • 3
  • 37
  • 47
  • Yes, I'll even overtly state that fragility is the most problematic issue I see with the strings approach. But that's not the only issue – and again (see my previous comment on Golo Roden's similar answer), I am familiar with several weakly typed languages as well (mostly scripting stuff, as they tend to be). One other example besides fragility is trackability, facilitated by the semantic payload carried by the act of using this constant instead of the other (e.g. MINUTES_IN_HOURS as opposed to SECONDS_IN_MINUTES). – Bogdan Stăncescu Mar 10 '17 at 09:35
  • That notion of trackability is a very valid concern, especially for numeric constants. However, I don't think that JavaScript developers tend to pass around unlabeled numbers as a best practice; I would say it is an accepted/encouraged coding style to describe/distinguish locally-used constants like the two you gave above. I would be interested to see if you could come up with a similar example involving strings, though, which are much more commonly used without variable labels. You posed a very good question here today and I hope it generates an interesting conversation! – gyre Mar 10 '17 at 09:43
  • Well, the "data" event in the OP could be an example. Say you designed a library which handled some sort of data, and the only event you triggered was 'data'. Again, I'm new to node, but I assume 'data' is a common event generated by several libraries – so people using your library would end up using that same string to identify "data-like" events coming in from several libraries. If at some point your original library expanded scope and you wanted to differentiate between different types of data events, coders using it would have a hard time identifying which events need investigating. – Bogdan Stăncescu Mar 10 '17 at 09:55
  • These limitations in comment size are quite frustrating, I wanted to explain my point better in the reply above – but I'm sure you get the gist of it. A couple more (related) reasons I thought of: code hygiene and documentation. There's a huge difference in maintainability between having a collection of constants defined up front at the top of the library (or anywhere, for that matter), and simply using various ad hoc strings throughout the code base. – Bogdan Stăncescu Mar 10 '17 at 12:06
  • I get what you mean with the collision problem between different libraries and modules. However, subscribing and listening to an event is done on a *per-object* basis (one object's listeners would never receive updates when another object emits an event). Thus, as long as one object does not choose to emit two "kinds" of `data` events (which would be purposeful and easily avoidable bad design), I don't see how collisions could ever become a problem. – gyre Mar 10 '17 at 19:50
  • Each API that extends EventEmitter *should* extensively document exactly what events are emitted and when, but this may need to be done in the form of comments or an online reference rather than inspecting a library-wide constant list. – gyre Mar 10 '17 at 19:50