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.

6 Answers6

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.

gnasher729
  • 49,096
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.

Flater
  • 58,824
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.

JacquesB
  • 61,955
  • 21
  • 135
  • 189
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.

htor
  • 149
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.

-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.

crisam
  • 1
  • 1