Is there a better way to avoid if/then/else? - javascript

Background
We have a request object that contains information. That specific object has a field called partnerId which determines what we are going to do with the request.
A typical approach would be a gigantic if/then/else:
function processRequest( request ){
if( request.partnerId === 1 ){
//code here
}else if( request.partnerId === 23 ){
//code here
}
//and so on. This would be a **huge** if then else.
}
This approach has two main problems:
This function would be huge. Huge functions are a code smell (explaining why next) but mainly they become very hard to read and maintain very quickly.
This function would do more than one thing. This is a problem. Good coding practices recommend that 1 function should do only 1 thing.
Our solution
To bypass the previous problems, I challenged my co-worker to come up with a different solution, and he came up with a function that dynamically builds the name of the function we want to use and calls it. Sounds complicated but this code will clarify it:
const functionHolder = {
const p1 = request => {
//deals with request
};
const p23 = request => {
//deals with request
};
return { p1, p23 };
};
const processRequest = request => {
const partnerId = request.partnerId;
const result = functionHolder[`p${partnerId}`](request);
return result;
};
Problems
This solution has advantages over the previous one:
There is no main function with an huge gigantic if then else.
Each execution path is not a single function that does one thing only
However it also has a few problems:
We are using an object functionHolder which is in reality useless. p1 and p23 don't share anything in common, we just use this object because we don't know how else we can build the function's name dynamically and call it.
There is no else case. If we get an incorrect parameter the code blows.
Out eslint with rule non-used-vars complains that p1 and p23 are not being used and we don't know how to fix it ( https://eslint.org/docs/rules/no-unused-vars ).
The last problem, gives us the impression that perhaps this solution is not so great. Perhaps this pattern to avoid an if then else has some evil to it that we are yet to find.
Questions
Is there any other pattern we can use to avoid huge if then else statements ( or switch cases )?
Is there a way to get rid of the functionHolder object?
Should we change the pattern or fix the rule?
Looking forward to any feedback!

You can get rid of the unused variables by never declaring them in the first place:
const functionHolder = {
p1: request => {
//deals with request
},
p23: request => {
//deals with request
};
};
const processRequest = request => {
const partnerId = request.partnerId;
const method = functionHolder[`p${partnerId}`]
if(method) // Is there a method for `partnerId`?
return method(request);
return null; // No method found. Return `null` or call your default handler here.
};
To answer your points:
Yeap, as shown above.
Not without some kind of object.
That's up to you. Whatever you prefer.

Perhaps I'm not understanding the question properly, but why not an object to hold the methods?
const functionHolder = {
1: function(request) {
// do something
},
23: function(request) {
// do something
},
_default: function(request) {
// do something
}
}
function processRequest(request) {
(functionHolder[request.partnerId] || functionHolder._default)(request)
}
Explanation:
The object functionHolder contains each of the methods used to deal with a given request.
The keys of functionHolder (e.g. 1) correspond directly to the values of request.partnerId, and the values of these members are the appropriate methods.
The function processRequest "selects" the appropriate method in functionHolder (i.e. object[key]), and calls this method with the request as the parameter (i.e. method(parameter)).
We also have a default method, under the key _default, if request.partnerId does not match any existing key. Given a || b; if a is "falsy", in this case undefined (because there is no corresponding member of the object), evaluate to b.
If you are concerned about making functionHolder "bloated", then you can separate each of the methods:
const p1 = request => {
// do something
}
const p23 = request => {
// do something
}
const _default = request => {
// do something
}
And then combine them into a "summary" object of sorts.
const functionHolder = {
1: p1,
23: p23,
_default: _default
}
processRequest remains the same as above.
This adds a lot of global variables though.
Another advantage is you can import / change / declare methods on the fly. e.g.
functionHolder[1] = p1b // where p1b is another request handler (function) for `request.partnerId` = 1
functionHolder[5] = p5 // where p5 is a request handler (function) that has not yet been declared for `request.partnerId` = 5
Combining the above, without having to declare many global variables while also being able to separate the declaration of each method:
const functionHolder = {}
functionHolder._default = request => {
// do something
}
functionHolder[1] = request => {
// do something
}
functionHolder[23] = request => {
// do something
}
processRequest remains the same as above.
You just have to be sure that the methods are "loaded in" to functionHolder before you call processRequest.

Related

RegEx expression into a constant variable [duplicate]

Is there a way to use constants in JavaScript?
If not, what's the common practice for specifying variables that are used as constants?
Since ES2015, JavaScript has a notion of const:
const MY_CONSTANT = "some-value";
This will work in pretty much all browsers except IE 8, 9 and 10. Some may also need strict mode enabled.
You can use var with conventions like ALL_CAPS to show that certain values should not be modified if you need to support older browsers or are working with legacy code:
var MY_CONSTANT = "some-value";
Are you trying to protect the variables against modification? If so, then you can use a module pattern:
var CONFIG = (function() {
var private = {
'MY_CONST': '1',
'ANOTHER_CONST': '2'
};
return {
get: function(name) { return private[name]; }
};
})();
alert('MY_CONST: ' + CONFIG.get('MY_CONST')); // 1
CONFIG.MY_CONST = '2';
alert('MY_CONST: ' + CONFIG.get('MY_CONST')); // 1
CONFIG.private.MY_CONST = '2'; // error
alert('MY_CONST: ' + CONFIG.get('MY_CONST')); // 1
Using this approach, the values cannot be modified. But, you have to use the get() method on CONFIG :(.
If you don't need to strictly protect the variables value, then just do as suggested and use a convention of ALL CAPS.
The const keyword is in the ECMAScript 6 draft but it thus far only enjoys a smattering of browser support: http://kangax.github.io/compat-table/es6/. The syntax is:
const CONSTANT_NAME = 0;
"use strict";
var constants = Object.freeze({
"π": 3.141592653589793 ,
"e": 2.718281828459045 ,
"i": Math.sqrt(-1)
});
constants.π; // -> 3.141592653589793
constants.π = 3; // -> TypeError: Cannot assign to read only property 'π' …
constants.π; // -> 3.141592653589793
delete constants.π; // -> TypeError: Unable to delete property.
constants.π; // -> 3.141592653589793
See Object.freeze. You can use const if you want to make the constants reference read-only as well.
IE does support constants, sort of, e.g.:
<script language="VBScript">
Const IE_CONST = True
</script>
<script type="text/javascript">
if (typeof TEST_CONST == 'undefined') {
const IE_CONST = false;
}
alert(IE_CONST);
</script>
ECMAScript 5 does introduce Object.defineProperty:
Object.defineProperty (window,'CONSTANT',{ value : 5, writable: false });
It's supported in every modern browser (as well as IE ≥ 9).
See also: Object.defineProperty in ES5?
No, not in general. Firefox implements const but I know IE doesn't.
#John points to a common naming practice for consts that has been used for years in other languages, I see no reason why you couldn't use that. Of course that doesn't mean someone will not write over the variable's value anyway. :)
In JavaScript, my preference is to use functions to return constant values.
function MY_CONSTANT() {
return "some-value";
}
alert(MY_CONSTANT());
Mozillas MDN Web Docs contain good examples and explanations about const. Excerpt:
// define MY_FAV as a constant and give it the value 7
const MY_FAV = 7;
// this will throw an error - Uncaught TypeError: Assignment to constant variable.
MY_FAV = 20;
But it is sad that IE9/10 still does not support const. And the reason it's absurd:
So, what is IE9 doing with const? So
far, our decision has been to not
support it. It isn’t yet a consensus
feature as it has never been available
on all browsers.
...
In the end, it seems like the best
long term solution for the web is to
leave it out and to wait for
standardization processes to run their
course.
They don't implement it because other browsers didn't implement it correctly?! Too afraid of making it better? Standards definitions or not, a constant is a constant: set once, never changed.
And to all the ideas: Every function can be overwritten (XSS etc.). So there is no difference in var or function(){return}. const is the only real constant.
Update:
IE11 supports const:
IE11 includes support for the well-defined and commonly used features of the emerging ECMAScript 6 standard including let, const, Map, Set, and WeakMap, as well as __proto__ for improved interoperability.
If you don't mind using functions:
var constant = function(val) {
return function() {
return val;
}
}
This approach gives you functions instead of regular variables, but it guarantees* that no one can alter the value once it's set.
a = constant(10);
a(); // 10
b = constant(20);
b(); // 20
I personally find this rather pleasant, specially after having gotten used to this pattern from knockout observables.
*Unless someone redefined the function constant before you called it
with the "new" Object api you can do something like this:
var obj = {};
Object.defineProperty(obj, 'CONSTANT', {
configurable: false
enumerable: true,
writable: false,
value: "your constant value"
});
take a look at this on the Mozilla MDN for more specifics. It's not a first level variable, as it is attached to an object, but if you have a scope, anything, you can attach it to that. this should work as well.
So for example doing this in the global scope will declare a pseudo constant value on the window (which is a really bad idea, you shouldn't declare global vars carelessly)
Object.defineProperty(this, 'constant', {
enumerable: true,
writable: false,
value: 7,
configurable: false
});
> constant
=> 7
> constant = 5
=> 7
note: assignment will give you back the assigned value in the console, but the variable's value will not change
Group constants into structures where possible:
Example, in my current game project, I have used below:
var CONST_WILD_TYPES = {
REGULAR: 'REGULAR',
EXPANDING: 'EXPANDING',
STICKY: 'STICKY',
SHIFTING: 'SHIFTING'
};
Assignment:
var wildType = CONST_WILD_TYPES.REGULAR;
Comparision:
if (wildType === CONST_WILD_TYPES.REGULAR) {
// do something here
}
More recently I am using, for comparision:
switch (wildType) {
case CONST_WILD_TYPES.REGULAR:
// do something here
break;
case CONST_WILD_TYPES.EXPANDING:
// do something here
break;
}
IE11 is with new ES6 standard that has 'const' declaration.
Above works in earlier browsers like IE8, IE9 & IE10.
Forget IE and use the const keyword.
You can easily equip your script with a mechanism for constants that can be set but not altered. An attempt to alter them will generate an error.
/* author Keith Evetts 2009 License: LGPL
anonymous function sets up:
global function SETCONST (String name, mixed value)
global function CONST (String name)
constants once set may not be altered - console error is generated
they are retrieved as CONST(name)
the object holding the constants is private and cannot be accessed from the outer script directly, only through the setter and getter provided
*/
(function(){
var constants = {};
self.SETCONST = function(name,value) {
if (typeof name !== 'string') { throw new Error('constant name is not a string'); }
if (!value) { throw new Error(' no value supplied for constant ' + name); }
else if ((name in constants) ) { throw new Error('constant ' + name + ' is already defined'); }
else {
constants[name] = value;
return true;
}
};
self.CONST = function(name) {
if (typeof name !== 'string') { throw new Error('constant name is not a string'); }
if ( name in constants ) { return constants[name]; }
else { throw new Error('constant ' + name + ' has not been defined'); }
};
}())
// ------------- demo ----------------------------
SETCONST( 'VAT', 0.175 );
alert( CONST('VAT') );
//try to alter the value of VAT
try{
SETCONST( 'VAT', 0.22 );
} catch ( exc ) {
alert (exc.message);
}
//check old value of VAT remains
alert( CONST('VAT') );
// try to get at constants object directly
constants['DODO'] = "dead bird"; // error
Yet there is no exact cross browser predefined way to do it , you can achieve it by controlling the scope of variables as showed on other answers.
But i will suggest to use name space to distinguish from other variables. this will reduce the chance of collision to minimum from other variables.
Proper namespacing like
var iw_constant={
name:'sudhanshu',
age:'23'
//all varibale come like this
}
so while using it will be iw_constant.name or iw_constant.age
You can also block adding any new key or changing any key inside iw_constant using Object.freeze method. However its not supported on legacy browser.
ex:
Object.freeze(iw_constant);
For older browser you can use polyfill for freeze method.
If you are ok with calling function following is best cross browser way to define constant. Scoping your object within a self executing function and returning a get function for your constants
ex:
var iw_constant= (function(){
var allConstant={
name:'sudhanshu',
age:'23'
//all varibale come like this
};
return function(key){
allConstant[key];
}
};
//to get the value use
iw_constant('name') or iw_constant('age')
** In both example you have to be very careful on name spacing so that your object or function shouldn't be replaced through other library.(If object or function itself wil be replaced your whole constant will go)
For a while, I specified "constants" (which still weren't actually constants) in object literals passed through to with() statements. I thought it was so clever. Here's an example:
with ({
MY_CONST : 'some really important value'
}) {
alert(MY_CONST);
}
In the past, I also have created a CONST namespace where I would put all of my constants. Again, with the overhead. Sheesh.
Now, I just do var MY_CONST = 'whatever'; to KISS.
My opinion (works only with objects).
var constants = (function(){
var a = 9;
//GLOBAL CONSTANT (through "return")
window.__defineGetter__("GCONST", function(){
return a;
});
//LOCAL CONSTANT
return {
get CONST(){
return a;
}
}
})();
constants.CONST = 8; //9
alert(constants.CONST); //9
Try! But understand - this is object, but not simple variable.
Try also just:
const a = 9;
I too have had a problem with this. And after quite a while searching for the answer and looking at all the responses by everybody, I think I've come up with a viable solution to this.
It seems that most of the answers that I've come across is using functions to hold the constants. As many of the users of the MANY forums post about, the functions can be easily over written by users on the client side. I was intrigued by Keith Evetts' answer that the constants object can not be accessed by the outside, but only from the functions on the inside.
So I came up with this solution:
Put everything inside an anonymous function so that way, the variables, objects, etc. cannot be changed by the client side. Also hide the 'real' functions by having other functions call the 'real' functions from the inside. I also thought of using functions to check if a function has been changed by a user on the client side. If the functions have been changed, change them back using variables that are 'protected' on the inside and cannot be changed.
/*Tested in: IE 9.0.8; Firefox 14.0.1; Chrome 20.0.1180.60 m; Not Tested in Safari*/
(function(){
/*The two functions _define and _access are from Keith Evetts 2009 License: LGPL (SETCONST and CONST).
They're the same just as he did them, the only things I changed are the variable names and the text
of the error messages.
*/
//object literal to hold the constants
var j = {};
/*Global function _define(String h, mixed m). I named it define to mimic the way PHP 'defines' constants.
The argument 'h' is the name of the const and has to be a string, 'm' is the value of the const and has
to exist. If there is already a property with the same name in the object holder, then we throw an error.
If not, we add the property and set the value to it. This is a 'hidden' function and the user doesn't
see any of your coding call this function. You call the _makeDef() in your code and that function calls
this function. - You can change the error messages to whatever you want them to say.
*/
self._define = function(h,m) {
if (typeof h !== 'string') { throw new Error('I don\'t know what to do.'); }
if (!m) { throw new Error('I don\'t know what to do.'); }
else if ((h in j) ) { throw new Error('We have a problem!'); }
else {
j[h] = m;
return true;
}
};
/*Global function _makeDef(String t, mixed y). I named it makeDef because we 'make the define' with this
function. The argument 't' is the name of the const and doesn't need to be all caps because I set it
to upper case within the function, 'y' is the value of the value of the const and has to exist. I
make different variables to make it harder for a user to figure out whats going on. We then call the
_define function with the two new variables. You call this function in your code to set the constant.
You can change the error message to whatever you want it to say.
*/
self._makeDef = function(t, y) {
if(!y) { throw new Error('I don\'t know what to do.'); return false; }
q = t.toUpperCase();
w = y;
_define(q, w);
};
/*Global function _getDef(String s). I named it getDef because we 'get the define' with this function. The
argument 's' is the name of the const and doesn't need to be all capse because I set it to upper case
within the function. I make a different variable to make it harder for a user to figure out whats going
on. The function returns the _access function call. I pass the new variable and the original string
along to the _access function. I do this because if a user is trying to get the value of something, if
there is an error the argument doesn't get displayed with upper case in the error message. You call this
function in your code to get the constant.
*/
self._getDef = function(s) {
z = s.toUpperCase();
return _access(z, s);
};
/*Global function _access(String g, String f). I named it access because we 'access' the constant through
this function. The argument 'g' is the name of the const and its all upper case, 'f' is also the name
of the const, but its the original string that was passed to the _getDef() function. If there is an
error, the original string, 'f', is displayed. This makes it harder for a user to figure out how the
constants are being stored. If there is a property with the same name in the object holder, we return
the constant value. If not, we check if the 'f' variable exists, if not, set it to the value of 'g' and
throw an error. This is a 'hidden' function and the user doesn't see any of your coding call this
function. You call the _getDef() function in your code and that function calls this function.
You can change the error messages to whatever you want them to say.
*/
self._access = function(g, f) {
if (typeof g !== 'string') { throw new Error('I don\'t know what to do.'); }
if ( g in j ) { return j[g]; }
else { if(!f) { f = g; } throw new Error('I don\'t know what to do. I have no idea what \''+f+'\' is.'); }
};
/*The four variables below are private and cannot be accessed from the outside script except for the
functions inside this anonymous function. These variables are strings of the four above functions and
will be used by the all-dreaded eval() function to set them back to their original if any of them should
be changed by a user trying to hack your code.
*/
var _define_func_string = "function(h,m) {"+" if (typeof h !== 'string') { throw new Error('I don\\'t know what to do.'); }"+" if (!m) { throw new Error('I don\\'t know what to do.'); }"+" else if ((h in j) ) { throw new Error('We have a problem!'); }"+" else {"+" j[h] = m;"+" return true;"+" }"+" }";
var _makeDef_func_string = "function(t, y) {"+" if(!y) { throw new Error('I don\\'t know what to do.'); return false; }"+" q = t.toUpperCase();"+" w = y;"+" _define(q, w);"+" }";
var _getDef_func_string = "function(s) {"+" z = s.toUpperCase();"+" return _access(z, s);"+" }";
var _access_func_string = "function(g, f) {"+" if (typeof g !== 'string') { throw new Error('I don\\'t know what to do.'); }"+" if ( g in j ) { return j[g]; }"+" else { if(!f) { f = g; } throw new Error('I don\\'t know what to do. I have no idea what \\''+f+'\\' is.'); }"+" }";
/*Global function _doFunctionCheck(String u). I named it doFunctionCheck because we're 'checking the functions'
The argument 'u' is the name of any of the four above function names you want to check. This function will
check if a specific line of code is inside a given function. If it is, then we do nothing, if not, then
we use the eval() function to set the function back to its original coding using the function string
variables above. This function will also throw an error depending upon the doError variable being set to true
This is a 'hidden' function and the user doesn't see any of your coding call this function. You call the
doCodeCheck() function and that function calls this function. - You can change the error messages to
whatever you want them to say.
*/
self._doFunctionCheck = function(u) {
var errMsg = 'We have a BIG problem! You\'ve changed my code.';
var doError = true;
d = u;
switch(d.toLowerCase())
{
case "_getdef":
if(_getDef.toString().indexOf("z = s.toUpperCase();") != -1) { /*do nothing*/ }
else { eval("_getDef = "+_getDef_func_string); if(doError === true) { throw new Error(errMsg); } }
break;
case "_makedef":
if(_makeDef.toString().indexOf("q = t.toUpperCase();") != -1) { /*do nothing*/ }
else { eval("_makeDef = "+_makeDef_func_string); if(doError === true) { throw new Error(errMsg); } }
break;
case "_define":
if(_define.toString().indexOf("else if((h in j) ) {") != -1) { /*do nothing*/ }
else { eval("_define = "+_define_func_string); if(doError === true) { throw new Error(errMsg); } }
break;
case "_access":
if(_access.toString().indexOf("else { if(!f) { f = g; }") != -1) { /*do nothing*/ }
else { eval("_access = "+_access_func_string); if(doError === true) { throw new Error(errMsg); } }
break;
default:
if(doError === true) { throw new Error('I don\'t know what to do.'); }
}
};
/*Global function _doCodeCheck(String v). I named it doCodeCheck because we're 'doing a code check'. The argument
'v' is the name of one of the first four functions in this script that you want to check. I make a different
variable to make it harder for a user to figure out whats going on. You call this function in your code to check
if any of the functions has been changed by the user.
*/
self._doCodeCheck = function(v) {
l = v;
_doFunctionCheck(l);
};
}())
It also seems that security is really a problem and there is not way to 'hide' you programming from the client side. A good idea for me is to compress your code so that it is really hard for anyone, including you, the programmer, to read and understand it. There is a site you can go to: http://javascriptcompressor.com/. (This is not my site, don't worry I'm not advertising.) This is a site that will let you compress and obfuscate Javascript code for free.
Copy all the code in the above script and paste it into the top textarea on the javascriptcompressor.com page.
Check the Base62 encode checkbox, check the Shrink Variables checkbox.
Press the Compress button.
Paste and save it all in a .js file and add it to your page in the head of your page.
Clearly this shows the need for a standardized cross-browser const keyword.
But for now:
var myconst = value;
or
Object['myconst'] = value;
Both seem sufficient and anything else is like shooting a fly with a bazooka.
I use const instead of var in my Greasemonkey scripts, but it is because they will run only on Firefox...
Name convention can be indeed the way to go, too (I do both!).
In JavaScript my practice has been to avoid constants as much as I can and use strings instead. Problems with constants appear when you want to expose your constants to the outside world:
For example one could implement the following Date API:
date.add(5, MyModule.Date.DAY).add(12, MyModule.Date.HOUR)
But it's much shorter and more natural to simply write:
date.add(5, "days").add(12, "hours")
This way "days" and "hours" really act like constants, because you can't change from the outside how many seconds "hours" represents. But it's easy to overwrite MyModule.Date.HOUR.
This kind of approach will also aid in debugging. If Firebug tells you action === 18 it's pretty hard to figure out what it means, but when you see action === "save" then it's immediately clear.
Okay, this is ugly, but it gives me a constant in Firefox and Chromium, an inconstant constant (WTF?) in Safari and Opera, and a variable in IE.
Of course eval() is evil, but without it, IE throws an error, preventing scripts from running.
Safari and Opera support the const keyword, but you can change the const's value.
In this example, server-side code is writing JavaScript to the page, replacing {0} with a value.
try{
// i can haz const?
eval("const FOO='{0}';");
// for reals?
var original=FOO;
try{
FOO='?NO!';
}catch(err1){
// no err from Firefox/Chrome - fails silently
alert('err1 '+err1);
}
alert('const '+FOO);
if(FOO=='?NO!'){
// changed in Sf/Op - set back to original value
FOO=original;
}
}catch(err2){
// IE fail
alert('err2 '+err2);
// set var (no var keyword - Chrome/Firefox complain about redefining const)
FOO='{0}';
alert('var '+FOO);
}
alert('FOO '+FOO);
What is this good for? Not much, since it's not cross-browser. At best, maybe a little peace of mind that at least some browsers won't let bookmarklets or third-party script modify the value.
Tested with Firefox 2, 3, 3.6, 4, Iron 8, Chrome 10, 12, Opera 11, Safari 5, IE 6, 9.
If it is worth mentioning, you can define constants in angular using $provide.constant()
angularApp.constant('YOUR_CONSTANT', 'value');
An improved version of Burke's answer that lets you do CONFIG.MY_CONST instead of CONFIG.get('MY_CONST').
It requires IE9+ or a real web browser.
var CONFIG = (function() {
var constants = {
'MY_CONST': 1,
'ANOTHER_CONST': 2
};
var result = {};
for (var n in constants)
if (constants.hasOwnProperty(n))
Object.defineProperty(result, n, { value: constants[n] });
return result;
}());
* The properties are read-only, only if the initial values are immutable.
JavaScript ES6 (re-)introduced the const keyword which is supported in all major browsers.
Variables declared via const cannot be re-declared or re-assigned.
Apart from that, const behaves similar to let.
It behaves as expected for primitive datatypes (Boolean, Null, Undefined, Number, String, Symbol):
const x = 1;
x = 2;
console.log(x); // 1 ...as expected, re-assigning fails
Attention: Be aware of the pitfalls regarding objects:
const o = {x: 1};
o = {x: 2};
console.log(o); // {x: 1} ...as expected, re-assigning fails
o.x = 2;
console.log(o); // {x: 2} !!! const does not make objects immutable!
const a = [];
a = [1];
console.log(a); // 1 ...as expected, re-assigning fails
a.push(1);
console.log(a); // [1] !!! const does not make objects immutable
If you really need an immutable and absolutely constant object: Just use const ALL_CAPS to make your intention clear. It is a good convention to follow for all const declarations anyway, so just rely on it.
Another alternative is something like:
var constants = {
MY_CONSTANT : "myconstant",
SOMETHING_ELSE : 123
}
, constantMap = new function ConstantMap() {};
for(var c in constants) {
!function(cKey) {
Object.defineProperty(constantMap, cKey, {
enumerable : true,
get : function(name) { return constants[cKey]; }
})
}(c);
}
Then simply: var foo = constantMap.MY_CONSTANT
If you were to constantMap.MY_CONSTANT = "bar" it would have no effect as we're trying to use an assignment operator with a getter, hence constantMap.MY_CONSTANT === "myconstant" would remain true.
in Javascript already exists constants. You define a constant like this:
const name1 = value;
This cannot change through reassignment.
The keyword 'const' was proposed earlier and now it has been officially included in ES6. By using the const keyword, you can pass a value/string that will act as an immutable string.
Introducing constants into JavaScript is at best a hack.
A nice way of making persistent and globally accessible values in JavaScript would be declaring an object literal with some "read-only" properties like this:
my={get constant1(){return "constant 1"},
get constant2(){return "constant 2"},
get constant3(){return "constant 3"},
get constantN(){return "constant N"}
}
you'll have all your constants grouped in one single "my" accessory object where you can look for your stored values or anything else you may have decided to put there for that matter. Now let's test if it works:
my.constant1; >> "constant 1"
my.constant1 = "new constant 1";
my.constant1; >> "constant 1"
As we can see, the "my.constant1" property has preserved its original value. You've made yourself some nice 'green' temporary constants...
But of course this will only guard you from accidentally modifying, altering, nullifying, or emptying your property constant value with a direct access as in the given example.
Otherwise I still think that constants are for dummies.
And I still think that exchanging your great freedom for a small corner of deceptive security is the worst trade possible.
Rhino.js implements const in addition to what was mentioned above.

Javascript Proxy gives Illegal invocation error [duplicate]

What I was trying to accomplish. I wanted to share a single canvas (because what I'm doing is very heavy) and so I thought I'd make a limited resource manager. You'd ask it for the resource via promise, in this case a Canvas2DRenderingContext. It would wrap the context in a revokable proxy. When you're finished you are required to call release which both returns the canvas to the limited resource manager so it can give it to someone else AND it revokes the proxy so the user can't accidentally use the resource again.
Except when I make a proxy of a Canvas2DRenderingContext it fails.
const ctx = document.createElement('canvas').getContext('2d');
const proxy = new Proxy(ctx, {});
// try to change the width of the canvas via the proxy
test(() => { proxy.canvas.width = 100; }); // ERROR
// try to translate the origin of via the proxy
test(() => { proxy.translate(1, 2); }); // ERROR
function test(fn) {
try {
fn();
} catch (e) {
console.log("FAILED:", e, fn);
}
}
The code above generates Uncaught TypeError: Illegal invocation in Chrome and TypeError: 'get canvas' called on an object that does not implement interface CanvasRenderingContext2D. in Firefox
Is that an expected limitation of Proxy or is it a bug?
note: of course there are other solutions. I can remove the proxy and just not worry about it. I can also wrap the canvas in some JavaScript object that just exposes the functions I need and proxy that. I'm just more curious if this is supposed to work or not. This Mozilla blog post kind of indirectly suggests it's supposed to be possbile since it actually mentions using a proxy with an HTMLElement if only to point out it would certainly fail if you called someElement.appendChild(proxiedElement) but given the simple code above I'd expect it's actually not possible to meanfully wrap any DOM elements or other native objects.
Below is proof that Proxies work with plain JS objects. They work with class based (as in the functions are on the prototype chain). And they don't work with native objects.
const img = document.createElement('img')
const proxy = new Proxy(img, {});
console.log(proxy.src);
Also fails with the same error. where as they don't with JavaScript objects
function testNoOpProxy(obj, msg) {
log(msg, '------');
const proxy = new Proxy(obj, {});
check("get property:", () => proxy.width);
check("set property:", () => proxy.width = 456);
check("get property:", () => proxy.width);
check("call fn on object:", () => proxy.getContext('2d'));
}
function check(msg, fn) {
let success = true;
let r;
try {
r = fn();
} catch (e) {
success = false;
}
log(' ', success ? "pass" : "FAIL", msg, r, fn);
}
const test = {
width: 123,
getContext: function() {
return "test";
},
};
class Test {
constructor() {
this.width = 123;
}
getContext() {
return `Test width = ${this.width}`;
}
}
const testInst = new Test();
const canvas = document.createElement('canvas');
testNoOpProxy(test, 'plain object');
testNoOpProxy(testInst, 'class object');
testNoOpProxy(canvas, 'native object');
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0; }
Well FWIW the solution I choose was to wrap the canvas in a small class that does the thing I was using it for. Advantage is it's easier to test (since I can pass in a mock) and I can proxy that object no problem. Still, I'd like to know
Why doesn't Proxy work for native object?
Do any of the reasons Proxy doesn't work with native objects apply to situations with JavaScript objects?
Is it possible to get Proxy to work with native objects.
const handlers = {
get: (target, key) => key in target ? target[key] : undefined,
set: (target, key, value) => {
if (key in target) {
target[key] = value;
}
return value;
}
};
const { revoke, proxy } = Proxy.revocable(ctx, handlers);
// elsewhere
try {
proxy.canvas.width = 500;
} catch (e) { console.log("Access has been revoked", e); }
Something like that should do what you're expecting.
A revocable proxy, with handlers for get and set traps, for the context.
Just keep in mind that when an instance of Proxy.revocable() is revoked, any subsequent access of that proxy will throw, and thus everything now needs to use try/catch, in the case that it has, indeed, been revoked.
Just for fun, here's how you can do the exact same thing without fear of throwing (in terms of simply using the accessor; no guarantee for doing something wrong while you still have access):
const RevocableAccess = (item, revoked = false) => ({
access: f => revoked ? undefined : f(item),
revoke: () => { revoked = true; }
});
const { revoke, access: useContext } = RevocableAccess(ctx);
useContext(ctx => ctx.canvas.width = 500);
revoke();
useContext(ctx => ctx.canvas.width = 200); // never fires
Edit
As pointed out in the comments below, I completely neglected to test for the method calls on the host object, which, it turns out, are all protected. This comes down to weirdness in the host objects, which get to play by their own rules.
With a proxy as above, proxy.drawImage.apply(ctx, args) would work just fine.
This, however, is counter-intuitive.
Cases that I'm assuming fail here, are Canvas, Image, Audio, Video, Promise (for instance based methods) and the like. I haven't conferred with the spec on this part of Proxies, and whether this is a property-descriptor thing, or a host-bindings thing, but I'm going to assume that it's the latter, if not both.
That said, you should be able to override it with the following change:
const { proxy, revoke } = Proxy.revocable(ctx, {
get(object, key) {
if (!(key in object)) {
return undefined;
}
const value = object[key];
return typeof value === "function"
? (...args) => value.apply(object, args)
: value;
}
});
Here, I am still "getting" the method off of the original object, to call it.
It just so happens that in the case of the value being a function, I call bind to return a function that maintains the this relationship to the original context. Proxies usually handle this common JS issue.
...this causes its own security concern; someone could cache the value out, now, and have permanent access to, say, drawImage, by saying
const draw = proxy.drawImage;...
Then again, they already had the ability to save the real render context, just by saying
const ctx = proxy.canvas.getContext("2d");
...so I'm assuming some level of good-faith, here.
For a more secure solution, there are other fixes, though with canvas, unless it's in-memory only, the context is ultimately going to be available to anyone who can read the DOM.

Call function from another function with parameters passed from both functions

I have this load-more listener on a button that calls the functions and it works fine.
let moviesPage = 1;
let seriesPage = 1;
document.getElementById('load-more').addEventListener('click', () => {
if (document.querySelector('#movies.active-link')) {
moviesPage++;
getMovies(moviesPage);
//getMovies(genreId, moviesPage);
} else if (document.querySelector('#series.active-link')) {
seriesPage++;
getSeries(seriesPage);
}
});
Now I have another listener on a list of links that calls the following code. It takes the genreId from the event parameter to sent as an argument to the api call. Also works fine so far.
document.querySelector('.dropdown-menu').addEventListener('click',
getByGenre);
function getByGenre (e) {
const genreId = e.target.dataset.genre;
movie.movieGenre(genreId)
.then(movieGenreRes => {
ui.printMovieByGenre(movieGenreRes);
})
.catch(err => console.log(err));
};
What I want to do is to call getByGenre from the load-more listener while passing also the moviesPage argument as you can see on the commented code so it can also be passed to the api call.
What would be the best way to do that? I've looked into .call() and .bind() but I'm not sure if it's the right direction to look at or even how to implement it in this situation.
Short Answer
Kludge: Global State
The simplest, though not the most elegant, way for you to solve this problem right now is by using some global state.
Take a global selection object that holds the selected genreId. Make sure you declare the object literal before using it anywhere.
So, your code might look something like so:
var selection = { };
document.querySelector('.dropdown-menu').addEventListener('click',
getByGenre);
function getByGenre (e) {
const genreId = e.target.dataset.genre;
selection.genreId = genreId;
movie.movieGenre(...);
};
...
let moviesPage = 1;
let seriesPage = 1;
document.getElementById('load-more').addEventListener('click', () => {
if (document.querySelector('#movies.active-link')) {
...
if (selection.genreId !== undefined) {
getMovies(selection.genreId, moviesPage);
}
} else if (...)) {
...
}
});
Closure
A more elegant way for you to accomplish this is by using a closure, but for that I have to know your code structure a bit more. For now, global state like the above will work for you.
Longer Answer
Your concerns have not been separated. You are mixing up more than one concern in your objects.
For e.g. to load more movies, in your load-more listener, you call a function named getMovies. However, from within the .dropdown-menu listener, you call into a movie object's method via the getByGenre method.
Ideally, you want to keep your UI concerns (such as selecting elements by using a query selector or reading data from elements) separate from your actual business objects. So, a more extensible model would have been like below:
var movies = {
get: function(howMany) {
if (howMany === undefined) {
howMany = defaultNumberOfMoviesToGetPerCall;
}
if (movies.genreId !== undefined) {
// get only those movies of the selected genre
} else {
// get all kinds of movies
}
},
genreId : undefined,
defaultNumberOfMoviesToGetPerCall: 25
};
document.get...('.load-more').addEventListener('whatever', (e) => {
var moviesArray = movies.get();
// do UI things with the moviesArray
});
document.get...('.dropdown-menu').addEventListener('whatever', (e) => {
movies.genreId = e.target.dataset.genreId;
var moviesArray = movies.get();
// do UI things with the moviesArray
});

Console integration: get number of errors/warnings thrown?

So if you open up the inspector, you get this (if you're unlucky):
I'm building a tiny JS component which displays debugging information - is there any way to read the number of encountered errors and warnings so far?
A hacky solution I could come up with involves a bit of trickery by replacing the console.(error|log|warn) functions with my own, but I'm yet to test if it works for all cases (e.g. outside of code I own).
Is there a better way to do this?
As noted in this answer, it's generally not a good idea to change the behavior of native objects/methods. However, the following code should get you what you need in a fairly innocuous manner:
// Add this IIFE to your codebase:
(() => {
// Get all of the property names of the console:
const methodsToTrack = Object.keys(window.console);
// Create an object to collect total usage tallies in:
const usageRegistry = {};
for (let i = 0, j = methodsToTrack.length; i < j; i++) {
let methodName = methodsToTrack[i];
// If the property is not a method, don't touch it:
if(typeof window.console[methodName] !== 'function') {
continue;
}
// Cache the original console method here:
let consoleMethod = window.console[methodName];
// Overwrite console's method to increment the counter:
window.console[methodName] = function () {
// Defining registry properties here, so the registry only contains values for methods that were accessed:
usageRegistry[methodName] = usageRegistry[methodName] || 0;
// Execute the original method's behavior, capturing the returned value (if any) in a var, to return it at the end:
const returnedValue = consoleMethod(...arguments);
// Increment the usage registry for the executed method:
usageRegistry[methodName]++;
// Return the value the console's method would have returned, so the new method has the same signature as the old.
return returnedValue;
};
}
// Define a funciton to output the totals to a console log, then clean up after itself:
window.showConsoleTallies = function () {
window.console.log(usageRegistry);
usageRegistry['log']--;
}
})();
// Examples:
showConsoleTallies();
console.log('log 1');
console.error('error 1');
console.log('log 2');
console.warn('warn 1');
console.error('error 2');
console.log('log 3');
showConsoleTallies();
PS: That's the ECMA6 version, but feel free to run it through Babel if you'd like it to be compiled for use in older browsers.

Algorithmically identifying pure functions in javascript

Is it possible to determine whether or not a javascript function is "pure", using javascript?
A function is said to be pure when it behaves in a predictable way, in the sense that for each x, the function will always return the same associated y value (i.e a single-valued map).
For example, a pure function:
function pure(x) {
return x * x;
}
And impure:
var x = 0;
function impure(y) {
x = x + y;
return x++;
}
While it's easy to tell here that impure(0) !== impure(0), it isn't as apparent with a function such as:
function weird(x) {
if (x === "specificThing") {
return false;
} else {
return true;
}
}
or
var count = 0;
function surprise(x) {
count++;
if (count === 10e10 && x === 0) {
return true;
} else {
return false;
}
}
Another way of asking this is, is it possible to determine whether or not a javascript function is "impure", using javascript?
Theoretically it may be possible or impossible, but practically what steps can one take to start to determine this, maybe given a set of constraints or assumptions?
Another definition of purity includes the caveat that non-local variables may not be changed, but I'd like to consider that a separate problem. In this case we are considering functions that map consistent inputs to consistent outputs.
JavaScript is Turing complete, so it's just as able to parse and analyse JavaScript as any other programming language. So, the question is really: "can JavaScript functions be automatically tested for "purity", at all?"
Short answer
Only sometimes.
Long answer
For some functions, for which the AST is straight forward and all the symbols are contained, definitely. Something like function(X) { return X * X; } is provably pure (for primitive input) because the only variables used in the function body are variables that are passed in as function arguments. This function does not rely on any additional API calls, but pure arithmetics. We can most definitely show it's pure.
Things change when we allow arbitrary content, because JavaScript has no explicit types, and instead will happily type-coerce from complex to primitive data type (or even from primitive to primitive data type) when it needs to perform an operation that can't have that operation applied. Calling our above function with an object, instead of a number, performs further function calls underwater, and those functions are not guaranteed pure at all (see Andreas's answer on this, too)
The vast majority of JS functions are nothing like our simple function. For most functions, we need to prove not only that they're pure, but that all the functions they call internally are pure, too. Now we're running into the halting problem. Let's take a ridiculously simple example:
function(x) {
if (typeof window !== "undefined") {
return x * x;
}
return x * x * x;
}
Is this pure? Well, if we run this in the browser, then in the browser it's pure, since window is always defined. But in something like Node.js, it might be pure, but it might not be: we can't prove it is, nor can we prove it is not, because we cannot prove that this mysterious window variable exists when the function runs. While Node.js has no global window variable, we can trivially introduce one whenever we like, and the function's behaviour will change. Now we're suddenly faced with proving whether or not the entirety of our code will ever introduce a window variable (and that may be very creatively done, like global["win" + _abc] = true where _abc is the string "dow"). This is a lost cause.
The rabbit hole runs deep, and reading about the halting problem will make you appreciate how many difference faces the halting problem has.
Even your first function isn't pure, because in JavaScript * may invoke a ToNumber conversion, which may invoke arbitrary user code, if x happens to be an object with a user-defined toString or valueOf method, or somebody happened to monkey-patch Object.prototype.
The sad truth is that in JS almost nothing can be proved pure. The only operations that can never have side effects are ===, !==, !, &&, ||, and typeof. This is a huge problem for optimisations in compilers, btw.
sample code. limitation: can only *guess* if some code is pure = can only give a hint, but no guarantee
/* programmatically guess if some javascript code is pure or impure
npm install acorn acorn-walk
license is CC0-1.0 */
const acorn_parse = require("acorn").parse;
const acorn_walk = require("acorn-walk");
// the code to analyze
const content = `
['k1'].map(key => {
const val = data[key];
let local1;
var local2 = 2;
var local3, local4;
global1[key] = val; // find me
global2.key = val; // find me
global3.push(val); // find me
global4.pop(); // find me
global5.shift(); // find me
global6 = 'impure'; // find me
const local7 = global7[1234];
var local8 = global8.map(x => 2*x);
var local9 = global9.filter(Boolean);
const local10 = global10.pop(); // find me
local1 = 'ok';
local2.push('ok');
return [key, val];
})
`;
// method names for our educated guess
const write_method_set = new Set(['push', 'pop', 'shift']);
const read_method_set = new Set(['map', 'filter', 'reduce', 'forEach']);
const is_write_method = method_name => write_method_set.has(method_name);
const is_read_method = method_name => read_method_set.has(method_name);
const is_local = name => (name != undefined && local_var_set.has(name));
const get_src = node => content.substring(node.start, node.end);
function test_assign(node, left_name) {
if (left_name == undefined) {
console.log(`TODO implement: detect write access in:`);
console.dir(node);
return;
}
if (!is_local(left_name)) console.log(`impure write access to global ${left_name}: ${get_src(node)}`);
else console.log(`pure? write access to local ${left_name}: ${get_src(node)}`);
}
function test_call(node, left_name, method_name) {
if (left_name == undefined) {
console.log(`TODO implement: detect write access in:`)
console.dir(node);
return;
}
if (is_read_method(method_name)) return console.log(`pure? access to ${left_name}: ${get_src(node)}`);
if (!is_local(left_name)) {
if (is_write_method(method_name)) console.log(`impure write access to global ${left_name}: ${get_src(node)}`);
else console.log(`pure? access to global ${left_name}: ${get_src(node)}`);
}
else console.log(`pure? write access to local ${left_name}: ${get_src(node)}`)
}
const local_var_set = new Set();
// throws on syntax error
let ast = acorn_parse(content, { ecmaVersion: 2020, sourceType: "module" });
acorn_walk.full(ast, (node, state, type) => {
if (node.type == 'VariableDeclaration') {
node.declarations.forEach(d => {
local_var_set.add(d.id.name);
console.log(`declare local: ${d.id.name}`);
});
}
else if (node.type == 'AssignmentExpression') {
const left_name =
node.left.type == 'Identifier' ? node.left.name :
node.left.type == 'MemberExpression' ? node.left.object.name :
undefined
;
test_assign(node, left_name);
}
else if (node.type == 'CallExpression') {
if (node.callee.object.type == 'ArrayExpression') return; // simply ignore
const left_name =
node.callee.type == 'MemberExpression' ? node.callee.object.name :
undefined
;
const method_name =
node.callee.type == 'MemberExpression' ? node.callee.property.name :
undefined
;
test_call(node, left_name, method_name);
}
//else console.dir(node);
});
sample output
$ node test.js | grep impure
impure write access to global global1: global1[key] = val
impure write access to global global2: global2.key = val
impure write access to global global3: global3.push(val)
impure write access to global global4: global4.pop()
impure write access to global global5: global5.shift()
impure write access to global global6: global6 = 'impure'
impure write access to global global10: global10.pop()

Categories