How to dynamically add function objects to an object function map - javascript

Hey there StackOverflow people of the world! Thank you for helping me with my question, and I apologize if this question gets a bit long winded. I just want to be clear about all the details and constraints I am working with. I found a few other related questions but nothing that was really very clear about how to get around my specific problem, unless I am missing something. Related questions:[1, 2]
Question Setup:
This is what I have and how it works, my question will be about a problem I am having
I've got a object that I've filled with named functions. The purpose of the object map is to contain many functions calls from multiple files. I am calling each function a "business rule" and they are typically very small functions that do a singular action with well-defined inputs and outputs. It also lets me chain the function calls sequentially with the output from functionCall1 becoming the input functionCall2.
All of my business rule definitions up to this point have been in a set of files that reside in a sub-folder called "Framework", but what I am trying to do now is allow the "Client" to define their own business rules in their own files and their own object map of function calls. What I would like to do is add all of the function calls to a single shared data storage.
What I am trying to avoid doing:
I am NOT trying to serialize the function calls, neither am I trying to leverage the 'eval' capability of JS. I've tried working with this before and it gets really messy!
Also I DO NOT want to declare a "class" object or use the "this" keyword for this reason:
10-most-common-javascript-mistakes
What is working:
(NOTE: Greatly simplified as I currently have hundreds of "business rules")
// rulesLibrary.js
import * as stringParsing from './Rules/stringParsing';
export const rulesLibrary = {
['Echo']: (inputData, inputMetaData) => (inputData, inputMetaData),
// Business Rules
// ********************************
// StringParsing rules in order
// ********************************
['stringToBoolean']: (inputData, inputMetaData) => stringParsing.stringToBoolean(inputData, inputMetaData),
['stringToDataType']: (inputData, inputMetaData) => stringParsing.stringToDataType(inputData, inputMetaData),
}
// stringParsing.js
export const stringToBoolean = function(inputData, inputMetaData) {
var returnData;
// Function Body...
return returnData;
};
export const stringToDataType = function(inputData, inputMetaData) {
var returnData;
// Function Body...
return returnData;
};
// ruleBroker.js
import * as rules from './rulesLibrary';
export const processRules = function(inputData, inputMetaData, rulesToExecute) {
var returnData = inputData;
for (var rule in rulesToExecute) {
if (rulesToExecute.hasOwnProperty(rule)) {
var key = rule;
var value = rulesToExecute[key];
returnData = rules.rulesLibrary[value](returnData, inputMetaData);
}
}
return returnData;
};
You can see in the code above the rulesLibrary is defining the functions in an object rulesLibrary = {}; which is also exported. Then in the ruleBroker we are calling the associated function:
rules.rulesLibrary[value](returnData, inputMetaData)....and this works great.
My Goal
My goal is to rather than store all these functionName: functionCall on the rules.rulesLibrary, I want to store them on a singleton data storage object I am calling "D".
Here is the definition of "D":
// data.js
export var data = {};
What I have tried - Attempt 1
I first tried to assign all of the contents of the rules.rulesLibrary from the rulesLibrary.js directly to "D" like so in the ruleBroker.js file:
// NOTE: I am actually doing this inside a function so I can boot-strap the rules.rulesLibrary into `D`, before the application begins going about the business of calling business rules via the ruleBroker.
import * as rules from './rulesLibrary';
var D = require('../Resources/data');
D['BusinessRules'] = {};
D['BusinessRules'] = rules.rulesLibrary;
This did not work and attempting to console.log(JSON.stringify(D)); just gave me back:
D{BusinessRules} = {};
What I have tried -- Attempt 2
So I thought maybe I should try and define the business rules map named function calls directly on "D" like so in the rulesLibrary.js file:
// NOTE: I am again doing all of this inside a boot-strap function for the same reason as above.
export const initRulesLibrary = function() {
D['BusinessRules'] = {};
D['BusinessRules'] = {
['Echo']: (inputData, inputMetaData) => (inputData, inputMetaData),
// Business Rules
// ********************************
// StringParsing rules in order
// ********************************
['stringToBoolean']: (inputData, inputMetaData) => stringParsing.stringToBoolean(inputData, inputMetaData),
['stringToDataType']: (inputData, inputMetaData) => stringParsing.stringToDataType(inputData, inputMetaData),
}
};
Again I get the same thing, contents of D are: D{BusinessRules} = {}.
Maybe console.log in combination with JSON.stringify doesn't work with function-objects?
But then again, I do have rules that return a function-object and I have been able to stringify those function-objects in the past with this same code. Granted it's a function-object so I am not expecting it to look pretty when stringified, but that's not the point. The point should be that the function-object exists on 'D' and it clearly does not, what am I missing here? How can I get all my function-objects mapped on 'D' so that I can add/merge more function-object definitions to it?
Ultimately this is what I want to be able to do:
function addClientRules(clientRules) {
Object.assign(D['BusinessRules'], clientRules['BusinessRules']);
};
Such that D now contains all of the system-defined business rules & all of the client defined business rules. Then in the ruleBroker, I would just call whatever business rule like this:
export const processRules = function(inputData, inputMetaData, rulesToExecute) {
var returnData = inputData;
for (var rule in rulesToExecute) {
if (rulesToExecute.hasOwnProperty(rule)) {
var key = rule;
var value = rulesToExecute[key];
// OLD WAY:
// returnData = rules.rulesLibrary[value](returnData, inputMetaData);
// NEW WAY:
returnData = D['BusinessRules'][value](returnData, inputMetaData);
}
}
return returnData;
};
Any ideas? Thoughts? Edits? Rants? Am I at least on the right track?
Thank you again for your help! Hopefully this will help someone else too!! :-D

Turns out I was already doing everything correctly to begin with. It's just that console.log & JSON.stringify don't work well with a object map of functions.
The function maps do contain the function calls, just don't expect your console.log even with JSON.stringify to dump that data in any way. You have to proceed with making the call as if it is there and verify that the execution is successful by putting console logs in the function that calls the rule and additionally putting console logs in the rule that is to be executed.
It does work and it's pretty cool when it does!!
I hope this can help someone else, please comment if you have any additional questions and/or if I can provide additional solution details.
Log of successful execution:
c.ccustomEcho resolves as: customEcho
BEGIN warden.executeBusinessRule function
businessRule is: customEcho
ruleInput is: Calling Custom Echo from application
ruleMetaData is: Calling Custom Echo from application
BEGIN ruleBroker.processRules function
inputData is: "Calling Custom Echo from application"
inputMetaData is: "something-nothing"
rulesToExecute are: {"0":"customEcho"}
BEGIN clientStringParsing.customEcho function
inputData is: Calling Custom Echo from application
inputMetaData is: something-nothing
returnData is: Calling Custom Echo from application clientStringParsing.customEcho
END clientStringParsing.customEcho function
returnData is: "Calling Custom Echo from application clientStringParsing.customEcho"
END ruleBroker.processRules function
returnData is: Calling Custom Echo from application clientStringParsing.customEcho
END warden.executeBusinessRule function
Cheers
~Seth

Related

Is it possible to identify a role after building a Vuejs project?

I have a file that exports some functions in a Vuejs project, and I need to use them also in an external environment .. inComponent I know which function I should use by identifying by name and comparing with a .JSON file this works cool in the environment of development but when I build the project the functions are renamed as in the image:
Is there any other reference in these functions where I can identify them other than by name? any reference in memory I don't know? Thank you!
You can define a unique value in the body of each function and then when you have a reference to one of the functions in your list you can call the toString() method of the function reference to get the source code of the function - and then check whether the desired unique value is present in the code.
Something like this:
const myFunc1 = function (...)
{
const uniqueIdent = 'zvjbesvfexrxe3cg4g3ewumkaj2hrz9m';
.....
}
const myFunc2 = function (...)
{
const uniqueIdent = 'y4wxfjedrr3mh6k5ju2gcff6wxafjcz5';
.....
}
// make the list of functions globally available
window.myFuncList = { myFunc1, myFunc2 };
// try to find the uglyfied name of Func2
var key;
var realNameFunc2;
for (key in window.myFuncList)
{
if (window.myFuncList[key].toString().indexOf('y4wxfjedrr3mh6k5ju2gcff6wxafjcz5') !== -1)
{
realNameFunc2 = key;
break;
}
}
// you can now invoke your function as realNameFunc2(...)

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

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.

How to do chain interface using constructor functions in Javascript?

I want an chain-able interface similar to jQuery. Still all prototype tutorials, talks, jQuery source is not making this clear, so much appreciate help in solving this.
In jQuery I can call jQuery('.apple') but also jQuery.ajax('param') since jQuery seems to be a constructor function, and therefore can have properties such as the ajax function I assume?
What I want:
Postal('apple') // Return chain-able function/object/constructor thing
Postal('apple').send('China').fee(99) // Used like this
Postal.send // should be undefined, need product-selector
Postal.area // should be function, accepting 1 parameter
Postal.area('Europe') // Use like this
Postal.area('Asia')
Note above: The Area should be accessible for chain-methods such as send()
Functions that can use the "Global Accessor" product selector:
Postal('apple').send('China').fee(9) // Apples to China fee
Postal('apple').fee(9) // Apples to anywhere default fee
Postal('orange').send('USA').fee(9)
My attempt at solving it:
Comments is around my approach to understand and mimic jQuery's source:
// # Assume
// ES2016 and not to much performance downsides or object allocations
//This should become our Globalisk-jQuery-Accessor
var Postal
// Create a non-global scope inside here
(function() {
// Locals that are global for the postal-object-function
var p_area = 'global'
var p_destination = 'unknown'
var p_product = '<unknown product>'
// our main selector, like: jQuery('.container')
var postal = (product) => {
// We use the same postal-object but create a new instance that can
// handle initializations of something
return new postal.prototype.init(product)
}
// All functions that only callable with a product-selector
postal.prototype = {
init: (product) => {
console.log("Init ran "+product)
// here we return this in case of no arguments?
// should we return the base object with slightly different methods?
if (!arguments[0]) {
return this
}
// Make 'product' parameter local to this postal call but shared
// amongst all chaining functions
p_product = product // or?
}
}
area = (area) => {
p_area = area //Save for future calls
console.log("Area set to: " + area)
return
// No return since no chaining possible after area is set
}
send = (destination) => {
p_destination = destination //Save for future chaining
console.log("Sending to " + destination + "...")
return send
}
send.fee = function fee(amount) {
console.log("Setting "+p_destination+" send fee to " + amount + " in " +
p_area + " for product "+ p_product)
return fee
}
// Set so new instance has same "properties" as postal has itself
//Need something like this? postal.prototype.init.prototype = postal.prototype
Postal = postal // Set our global accessor, from now on it should be const
})()
// Call direct function without going trough selector
console.log( Postal.send === undefined )
Postal.area('Europe') // Change the Postal-global "current area" state
Postal('apple').send('China').fee(99) // Applies only to "Europe"
Postal.area('Asia')
Postal('apple').send('China').fee(33)
Postal('orange').send('USA').fee(9)
Expected output:
Assuming code is fixed to work as intended. I am running in node v5.3.0.
true
Area set to: Europe
init ran apple
Sending to China...
Setting China send fee to 99 in Europe for product apple
Area set to: Asia
Sending to China...
Setting China send fee to 33 in Asia for product apple
init ran orange
Sending to USA...
Setting USA send fee to 9 in Asia for product orange
This is easy to accomplish with two accessors such as:
PostalCore.area() for global-methods and Postal('product here') for product-selector-methods, so the essence of the question is really how to do this with a constructor function so one can have both on same object, like jQuery does - I also welcome arguments to why not :)

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.

Call a method after a callback and an event

I have a module with four functions that call one after the other. I am trying to follow the Revealing Module Pattern. One of the functions is public, the remaining are private. It goes like this:
publicMethod is called from another module
queryNames is called from publicMethod
execute(parameters, callback?, errback?) is called from queryNames
addNamesList is called as the callback? argument of execute
Several dijit/form/CheckBox's are created and the method querySegments is triggered onChange
querySegments needs to call a method of an object created in publicMethod.
The problem is in step 6, I can't reach the object created in step 1.
I have tried to use dojo hitch to define the callback? argument in step 3, but I can't get it to work. I tried putting this in its first argument, but even then I can't reach the required scope to call addNamesList.
Here is some code to demonstrate this issue.
define([
'dojo/dom',
'dijit/form/CheckBox',
'esri/layers/ArcGISDynamicMapServiceLayer',
'esri/tasks/query',
'esri/tasks/QueryTask',
'dojo/_base/lang'
],
function (
dom,
CheckBox,
ArcGISDynamicMapServiceLayer,
Query, QueryTask,
lang
) {
// ***************
// private methods
// ***************
// fetch names and call addNamesList to put the list in place
var queryNames = function (map, mapLayer) {
// new QueryTask(url, options?)
var queryTask = new QueryTask("url")
var query = new Query()
// execute(parameters, callback?, errback?)
// this callback passes an argument called featureSet
queryTask.execute(query, lang.hitch(map, "addNamesList", mapLayer), function(error) {console.log(error)})
}
// callback function of queryNames
var addNamesList = function (mapLayer, featureSet) {
console.log('addOplist')
var namesCount = featureSet.features.length
for (var i = 0; i <namesCount; i++) {
// work
var cbox = new CheckBox({
id: "cbox_" + i,
value: featureSet.features[i].attributes["someID"],
checked: false,
onChange: function (evt) {
querySegments(this.value, mapLayer)
}
})
cbox.placeAt("someDiv" + i, "first")
}
}
// triggered by the checkbox event
var querySegments = function (name, mapLayer) {
// build the query
var queryStatement = "someID = " + name
var layerDefinitions = [queryStatement]
// call a method of mapLayer
mapLayer.setLayerDefinitions(layerDefinitions)
}
// **************
// public methods
// **************
var publicMethod = function (map) {
var mapLayer = new ArcGISDynamicMapServiceLayer('restURL')
map.addLayer(mapServiceLayer)
queryNames(map, mapLayer)
return mapLayer
}
return {
publicMethod: publicMethod
}
}
)
You can see a more detailed explanation and a working example on this other (and more broad) question that I have put on Code Review.
I am new to JavaScript and I guess I still have a lot of issues with scoping, closures and callbacks.
I will deeply appreciate any input, including how to improve this question.
Edit
With this current implementation (with dojo hitch), no error is thrown. The method addNamesList is not called (nor errback, which I also don't understand why). I think this is because addNamesList is not on map's (hitch first argument) namespace. I tried to put this instead, but it makes no difference.
Before I decided to use hitch, the code looked like this:
var queryNames = function (map, mapLayer) {
...
queryTask.execute(query, addNamesList)
}
var addNamesList = function (featureSet) {
...
...
...
querySegments(this.value, mapLayer)
}
but then I couldn't reach mapLayer inside the method triggered by the check box event. It would throw Uncaught ReferenceError: mapLayer is not defined. That is why I tried to use hitch.
Javascript is asynchronous, so pretty much data coming from db, http requests or whatever is returned via callbacks. Here's what happens in your code:
public method calls queryNames
queryNames call addNamesList of map asynchronously and returns nothing
public method takes back control, meanwhile some stuff is going on with the addNamesList
mapLayer is returned untouched while some stuff is still going on in the background
So, to avoid this, you should return data from public method via callback, so you pass callback as the second parameter to the public method, then to the querySegments. Then, in the success callback of query, when you finally get the result ready, you do:
callback(mapLayer);
So, everything you should do is to pass this callback as deep as needed to the place where you have your mapLayer ready (so you've done with it everything you wanted), and then do a callback(mapLayer);.
This and this would probably explain better.
Best regards, Alexander

Categories