6

I am about to create my own event-driven API in JavaScript.

Most JavaScript that currently exists, e.g. in-browser JS for controlling the DOM, lets you register event handlers like this:

object.on('eventname', function handler() {});

'eventname' is basically a magic string, which is generally recommended against (right?)

But I see this pattern in all sorts of JavaScript libraries.

So in creating my own, should I follow the same pattern, or should I limit users to only something like:

object.onEventName(function handler() {
});

Or is there another way.

5
  • 1
    You're asking a lot of JavaScript. JavaScript itself uses strings for event names, so I'm not sure you can get around this anyway. Since JavaScript is a dynamically-typed language, it won't check these things for you like a statically-typed language compiler would; and since the handler is an inline anonymous function, I'm not sure the name even matters. Apr 10, 2019 at 0:04
  • @RobertHarvey I put the name for the handler to make my question more readable. "JavaScript itself uses strings for event names" — I know lots of JS historically does, but I have the opportunity to design my own API now and don't have to stick to what other APIs do (although that might be the best idea). I'm not sure what javascript being dynamically typed has to do with this.
    – minseong
    Apr 10, 2019 at 0:32
  • 4
    I'm not sure what javascript being dynamically typed has to do with this. -- The primary benefit of your pattern is so that a compiler can check the static types to make sure they line up. That doesn't happen in JavaScript. Apr 10, 2019 at 2:07
  • @RobertHarvey oh I thought the benefit was to avoid magic strings. If there's no recommendation style-wise for any pattern other than .on('string', handler) then I guess I will just use that
    – minseong
    Apr 10, 2019 at 3:26
  • 1
    One way is to use an enumeration. Another is to embed it in the function name. The specific approach you take will depend on your tools. JavaScript does not have a formal compiler, so you cannot lean on that. There are however unit tests, browser tests, static analysis tools, etc... figure out how to lean on these. The aim is for it to blindingly obvious when a change was not correctly propagated throughout the system. A magic string is rarely if every blindingly obvious when it is incorrect. JavaScript is an Typed language, why not create a Type to represent the event. .on(bobEvent(...)).
    – Kain0_0
    Apr 10, 2019 at 6:19

6 Answers 6

3

There is a simple test: If you misspell it, is there some mechanism that tells you, or do things just mysteriously not work? If something tells you visibly that you got it wrong then you are fine.

1

Or is there another way?

The answer to that question is that you need to understand the intention of the advice, because then you can see which kinds of solutions are valid solutions to the problem that the advice tries to fix.

The issue with magic strings is that developers have to know the right value to use. Use the wrong value, and it won't work Maybe there's a synonymous phrase that sounds just as valid (but isn't checked for), maybe the developer makes a non-obvious typo, ... whatever the reason is, using magic strings is something that forces the developer to either remember these strings exactly or constantly look up the correct string.

If you can provide a way for the developer to not have to remember the exact string, instead being able to select it from a premade list, then you've solved the core problem.

IntelliSense is a very common solution here. If you can make the options available via IntelliSense, then the developer can easily select the right option.
There are a few ways to achieve this. Creating specific methods (your solution) is one way. Another way could be to introduce an enum-like object that maps the magic strings to fields on the object, so a developer could do something like:

object.on(EventNames.Click, function handler() {});
object.on(EventNames.Hover, function handler() {});
object.on(EventNames.MyCustomEvent, function handler() {});

This solved the problem without needing you to redesign how JS designed its magic-string method syntax (which was a bad call by whoever designed it to be this way).

There are non-IntelliSense solutions to this as well, e.g. if your IDE has code-generating templates so you can have the dev render a specific type of handler. This is a bit more IDE-specific though and it only really helps you in creating new handlers, not so much in making changes to existing handlers.


That being said, Javascript does not have any sense of strong typing, so you can't reasonably expect the system to point out every mistake to you. This is the nature of JS and you can't fully fix that.

As long as your solution gives the developer some nice way to autocomplete the value without needing to remember the string by heart and manually type it; that's an improvement.

7
  • The issue with magic strings is that developers have to know the right value to use. This is why we have documentation. To use an API that accepts any value, you still need to know which value to use based on its actual effect – not just its spelling – regardless of enum, object, or literal being used. Autocompletion is only helpful once you already know that. Also, the code clarity in your example is worsened by introducing an unnatural level of indirection which now reads "on event names hover" instead of "on hover". What is gained in the end there if our goal is better DX?
    – htor
    Oct 24, 2023 at 11:07
  • @htor You're conflating the value of documentation with the issue that magic strings try to address. These are similar but not equivalent points. There's no point going back and forth on this, there is plenty of source material online that showcases the particular non-documentative problems that magic strings introduce.
    – Flater
    Oct 24, 2023 at 12:17
  • The problem is that it doesn’t work, and your tools can’t tell you why. Mistype a function name in a C++ program and the compiler tells you and you fix it.
    – gnasher729
    Oct 24, 2023 at 18:35
  • "The issue with magic strings is that developers have to know the right value to use." - it is the same with identifiers. If you write "klick" it won't work, but if you write EventNames.Klick it wont work either. In JavaScript identifiers are basically just syntax sugar over string anyway: EventNames.Click is equivalent to EventNames["Click"]. If you want to have it checked statically, use TypeScript which will also find typos in strings.
    – JacquesB
    Dec 8, 2023 at 16:47
  • @JacquesB "It's JavaScript so let's just throw all good practice advice out the window" is not a productive attitude. Furthermore, Intellisense still helps out for predetermined values such as field or method names, but not for magic strings.
    – Flater
    Dec 8, 2023 at 20:54
1

There is nothing wrong with having string event name like "click". It is not any more magic than the method name onClick.

The aversion towards strings is a bit of cargo-cult imported from statically typed languages. In a language like Java, a string is worse than an enum or other identifier, because strings are not type-checked at compile time. But this distinction is not relevant in a dynamically typed language like JavaScript. (And if you add a more powerful type checker like TypeScript, you can also have the strings type-checked statically.)

Unless you have a compelling reason to do otherwise, you should follow the patterns that are common in a language.

0

Using strings as part of your JavaScript API isn't recommended against or considered bad practice.

For example, I might have a function for renderering a notification on my screen. It takes as arguments the message itself and a position:

function notify(message, position = 'top') {}

Possible values for position could be: 'top', 'bottom', 'left', 'right', defaulting to 'top'. Event APIs are not different. A string is used to identify the type of event listened to:

function on(event, callback) {}

Here, possible values for event may be: 'open', 'close' and 'snooze'. This is the normal way to define and listen to events and is used throughout the browser and node APIs.

5
  • While JS may lack the kind of strong typing that would enforce the use of a non-magic-string approach, that doesn't mean that the magic string issue is not relevant for JS. The issue with magic strings has nothing to do with a specific language, the core focus is on the developer experience and how hard it is to have to remember and manually type out these string values constantly. Just because JS veterans have become accustomed to doing so does not mean that it's therefore a great design idea or that we shouldn't consider improving it.
    – Flater
    Oct 24, 2023 at 0:22
  • @Flater Autocompletion is helpful, but you still have to read documentation to understand how things work. A documented string is not a magic string, by definition. As I understand it, the issue with magic numbers/strings/etc. is that they appear without context. Autocompletion of literal values will provide you with correct values, but no more.
    – htor
    Oct 24, 2023 at 1:22
  • The core focus of the magic string guideline is not the lack of external documentation. External documentation only minimally mitigates one of the many drawbacks of magic strings. Many well-written answers on why magic strings are bad can be found here.
    – Flater
    Oct 24, 2023 at 1:46
  • @Flater why is it harder to remember and type a string like "click" compared to an identifier like "onClick"?
    – JacquesB
    Dec 8, 2023 at 16:41
  • @JacquesB: Because Intellisense provides you a list of available methods. For compiled languages, the compiler can also catch calls to non-existing implementations. You don't get that from magic strings.
    – Flater
    Dec 8, 2023 at 20:52
0

I think there is a typo in the code from description with this code snippet...

object.onEventName(function handler() {
});

...that it seems natural to be...

object.on.eventName(function handler() {
});

...to shift from the string form of event names to variable form of event names...

var object = (function(element) {
 
    var listen = function(eventName, handler) {
        element.addListener(eventName, () => { handler(); });
    };

    var context = {
        click: function(handler) { listen("click", handler || ()=>{}); }
        , mouse: {
            out: function(handler) { listen("mouseout", handler || ()=>{}); }
          , over: function(handler) { listen("mouseover", handler || ()=>{}); }
            .
            .
            .
        }
        .
        .
        .
    };

    return { on: context };
})(document.querySelector("element-selector"));

...so maybe if the typo is fixed following the fluent interface design pattern then it might work the way is intended to work.

1
  • That wasn't a typo in the question. If you have a reason to think .on.eventName is the best pattern overall, that would make a very interesting answer
    – minseong
    Nov 25, 2023 at 2:01
-2
function getJediStatus(jediName) {
  if (jediName === "Luke Skywalker") {
    return "This is a Jedi Master.";
  } else if (jediName === "Yoda") {
    return "This is a Legendary Jedi Master.";
  } else {
    return "Unknown Jedi Status.";
  }
}

In this code, "Luke Skywalker" and "Yoda" are like magic strings because they are hardcoded without clear context. It's as if you needed to know the Star Wars universe to understand the code. To make it more understandable, you could use constants or variables with meaningful names, like this:

const JEDI_MASTER = "Luke Skywalker";
const LEGENDARY_JEDI_MASTER = "Yoda";

function getJediStatus(jediName) {
  if (jediName === JEDI_MASTER) {
    return "This is a Jedi Master.";
  } else if (jediName === LEGENDARY_JEDI_MASTER) {
    return "This is a Legendary Jedi Master.";
  } else {
    return "Unknown Jedi Status.";
  }
}

Using magic strings in API development is generally discouraged for several reasons:

  1. Lack of Clarity: Magic strings can make your code less clear and more error-prone. It might not be immediately evident what those strings represent, leading to confusion and potential bugs.

  2. Limited Error Checking: Magic strings don't benefit from compile-time checks or code analysis tools, making it easier to introduce typos or errors that might not be caught until runtime.

  3. Maintainability: Magic strings can make your codebase less maintainable. When you need to update or replace certain strings, you have to hunt for every occurrence in your code, which can be time-consuming and error-prone.

  4. Code Reusability: Code that relies on magic strings is less reusable. If you want to use the same logic with different configurations, you'd need to modify the code in multiple places, making it harder to maintain.

To address these concerns, API developers are encouraged to follow best practices such as:

  1. Use Constants or Enums: Replace magic strings with constants or enums that clearly define the possible values or configurations. This makes your code more self-documenting and easier to maintain.

  2. API Documentation: Document your APIs comprehensively. Provide clear and concise explanations for any values that are part of your API, so other developers know how to use it correctly.

  3. Validation and Error Handling: Implement validation and error handling in your APIs to catch invalid input early and provide meaningful error messages to clients.

  4. Versioning: If you need to make changes to your API that may affect existing clients, use versioning to ensure backward compatibility. This allows older clients to continue functioning while newer clients can take advantage of new features or fixes.

  5. Testing: Thoroughly test your APIs, including testing different valid and invalid inputs, to ensure that they behave as expected.

By following these best practices, you can create more robust, maintainable, and developer-friendly APIs that are less prone to errors and easier for others to use.

5
  • 1
    The example given isn't wrong from a technical point of view, but the names and values used are problematic as it encodes Luke Skywalker as the only jedi master and Yoda as the only legendary Jedi master. Additionally, the checks in the "improved" version are trying to match a person's name to what is labelled as a jedi mastery status, which is in and of itself defined as a person's name. You're making the code more confusing, not less. While the cause of the confusion is not the core of the question being asked, this causes a distraction and makes it a bad example to use here.
    – Flater
    Oct 24, 2023 at 0:26
  • The rest of the answer is actually really good in terms of addressing the reasons why magic strings are bad. I think you're attracting downvotes because people first see the example code and judge the answer because of it.
    – Flater
    Oct 24, 2023 at 0:28
  • I disagree your comment suggest that because the name bothered you it would bother everyone that encounters the snippet. There are many people who do not no of star wars and wouldnt be bothered by such reference.
    – crisam
    Nov 8, 2023 at 21:54
  • 1
    I doubt that your answer was specifically only targeting people who aren't aware of Star Wars. Readability is not just about grammatical correctness, it also requires a reasonable consideration of how a reader will interpret. This is the same reason why var onyx = superman + carrot * envy is less readable than var totalCost = baseFee + pricePerPerson * amountOfPeople, even though the compiler sees no difference between the two. "It is logically correct" and "it is readable and easy to follow" are two very different (and regularly orthogonal) considerations.
    – Flater
    Nov 8, 2023 at 22:27
  • 1
    jediStatus would be a better example if it was looking for the name in various lists of Jedis organized by status, but it would still have the caveat that you need to know a Jedi's name in order to use the function. The consumer is still putting in a "magic string" from their perspective: "Am I supposed to use 'Darth Vader' or 'Anakin Skywalker' here?" Nov 24, 2023 at 7:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.