You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
Sven Slootweg 0c03af64ec Pass original value to lazy functions 5 years ago
README.md Pass original value to lazy functions 5 years ago
example.js Initial commit 5 years ago
index.js Pass original value to lazy functions 5 years ago
package.json 1.0.1 5 years ago

README.md

match-value

Since JS as a language does not (yet) have a match statement or expression, this package implements the same concept for the most common cases.

You specify a mapping of source values to result values, as an object, and match-value maps from one to the other, throwing an error if an invalid source value is specified, optionally having a catch-all for unknown values, and with support for lazily-produced result values.

In other words, it turns this:

let mode;

if (flag === "r") {
	mode = "readOnly";
} else if (flag === "rw") {
	mode = "readWrite";
} else if (flag === "a") {
	mode = "appendOnly";
} else {
	mode = `unknown:${someExpensiveCall(flag)}`;
}

... or this:

let mode;

switch (flag) {
	case "r":
		mode = "readOnly";
		break;
	case "rw":
		mode = "readWrite";
		break;
	case "a":
		mode = "appendOnly";
		break;
	default:
		mode = `unknown:${someExpensiveCall(flag)}`;
		break;
}

... into this:

const matchValue = require("match-value");

let mode = matchValue(flag, {
	r: "readOnly",
	rw: "readWrite",
	a: "appendOnly",
	_: () => `unknown:${someExpensiveCall(flag)}`
});

Less repetition, and a much clearer intention.

Limitations

  • Source values may only be strings. Any input that isn't a string will always throw an error (or hit the catch-all, if you've specified one).
    • This is unfortunately to do with a limitation of JS as a language, that makes it impossible to ergonomically specify mappings where the source value isn't a string key. If JS ever gets Map literals, this limitation can be fixed.
  • Source values may only be literal values. There's no type/shape matching, like you might be used to if you've used a match construct in another language.
    • Again, this is due to a language limitation.
  • This library is completely unaware of the existence of Promises or asynchronous functions; all results are expected to be produced synchronously.
    • Of course, it is completely valid to return a Promise as your result value, and structure your code like Promise.resolve(matchValue(input, { ... }) to consistently get a Promise out of it.

License, donations, and other boilerplate

Licensed under either the WTFPL or CC0, at your choice. In practice, that means it's more or less public domain, and you can do whatever you want with it. Giving credit is not required, but still very much appreciated! I'd love to hear from you if this module was useful to you.

Creating and maintaining open-source modules is a lot of work. A donation is also not required, but much appreciated! You can donate here.

Example

A full version of the example above:

"use strict";

const matchValue = require("match-value");

let flag = "a"; // Hardcoded for example

let mode = matchValue(flag, {
	r: "readOnly",
	rw: "readWrite",
	a: "appendOnly",
	_: () => `unknown:${someExpensiveCall(flag)}`
});

console.log(mode); // appendOnly

API

matchValue(input, mapping)

Converts the input to a result based on the mapping.

  • input: The value to convert.
  • mapping: An object that describes the mapping in the format { possibleInput: result }.
    • A possibleInput of _ defines a "catch-all" case; anything that doesn't match any of the other possible inputs, will trigger the catch-all case.
    • The result may be either a literal value, or a function that produces that value. If it's a function, the function will only be called when there is a match, and the return value of that call will be returned from the matchValue call. If you want to return functions rather than call them, see the .literal method below.
      • If a function is specified, it will be called with the original input as its first and only argument; this makes it easier to specify the mapping object in a different place from where it is used with matchValue, by not needing to have it in the same lexical scope.

Returns a result if a match was found, or the catch-all was hit. Throws an Error if no match was found and no catch-all was specified.

To throw a custom error type instead, define a catch-all function that throws that error, like so:

matchValue(input, {
	someKey: "someResult",
	someOtherKey: "someOtherResult",
	_: () => { throw new CustomError("Oh no!"); }
});

Note that if you're using an arrow function here, it must have curly braces around the throw; since a throw in JS is a statement, not an expression, it may not appear in a brace-less arrow function (which only accepts expressions).

matchValue.literal(input, mapping)

Exactly like matchValue above, but if the result value is a function, it will be returned as-is, rather than being called; that is, the function is the result.

Note that this also means that a custom error-throwing handler is not possible in this case.

Changelog

v1.1.0 (June 3, 2020)

  • Feature: A lazy function will now receive the original input value as its first and only argument.

v1.0.1 (June 3, 2020)

  • Bugfix: Fixed function handling; they were interpreted as literals in non-literal mode and vice versa. It now works correctly, according to what the documentation specifies.

v1.0.0 (March 23, 2020)

Initial release.