I use the following code which is working great but I wonder if in JS there is a way to avoid the if and to do it inside the loop, I want to use also lodash if it helps
for (provider in config.providers[0]) {
if (provider === "save") {
....
You can chain calls together using _.chain, filter by a value, and then use each to call a function for each filtered result. However, you have to add a final .value() call at the end for it to evaluate the expression you just built.
I'd argue that for short, simple conditional blocks, an if statement is easier and more readable. I'd use lodash- and more specifically chaining- if you are combining multiple operations or performing sophisticated filtering, sorting, etc. over an object or collection.
var providers = ['hello', 'world', 'save'];
_.chain(providers)
.filter(function(provider) {
return provider === 'save';
}).each(function(p) {
document.write(p); // your code here
}).value();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.js"></script>
Edit: My mistake; filter does not have an overload where you can just supply a literal value. If you want to do literal value checking you have to supply a function as in my amended answer above.
I'd argue that what you have there is pretty good, clean and readable, but since you mentioned lodash, I will give it a try.
_.each(_.filter(config.providers[0], p => p === 'save'), p => {
// Do something with p
...
});
Note that the arrow function/lambda of ECMAScript 6 doesn't come to Chrome until version 45.
Basically, you are testing to see if config.providers[0], which is an object, contains a property called save (or some other dynamic value, I'm using a variable called provider to store that value in my example code below).
You can use this instead of using a for .. in .. loop:
var provider = 'save';
if (config.providers[0][provider] !== undefined) {
...
}
Or using #initialxy's (better!) suggestion:
if (provider in config.providers[0]) {
...
}
How about:
for (provider in config.providers[0].filter(function(a) {return a === "save"}) {
...
}
Strategy, you are looking for some kind of strategy pattern as,
Currenlty the save is hardcoded but what will you do if its coming from other varible – Al Bundy
var actions = {
save: function() {
alert('saved with args: ' + JSON.stringify(arguments))
},
delete: function() {
alert('deleted')
},
default: function() {
alert('action not supported')
}
}
var config = {
providers: [{
'save': function() {
return {
action: 'save',
args: 'some arguments'
}
},
notSupported: function() {}
}]
}
for (provider in config.providers[0]) {
(actions[provider] || actions['default'])(config.providers[0][provider]())
}
Push „Run code snippet” button will shows two pop-ups - be carefull
It is not clearly stated by the original poster whether the desired output
should be a single save - or an array containing all occurrences of
save.
This answer shows a solution to the latter case.
const providers = ['save', 'hello', 'world', 'save'];
const saves = [];
_.forEach(_.filter(providers, elem => { return elem==='save' }),
provider => { saves.push(provider); });
console.log(saves);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.js"></script>
Related
I have a js file that looks for paths and rewrites the paths, and it has over 300 if/else statements. I am looking for ways to clean it up, but I also need to make sure that it is readable for the next person who will work on it. My first thought was to convert all of these if statements to either ternaries or switch statements, but I am not sure if that is the best approach. I am trying to balance the urge to write "better" code, but also I need to make sure it stays readable, and the current if/else structure reads pretty well.
Example:
if (request.uri.match(^\/path1\/.*$)) {
request.uri = request.uri.replace(/^\/path1\/(.*)$/) "newPath" + "$1");
} else if (request.uri.match(^\/path2\/.*$)) {
request.uri = request.uri.replace(/^\/path2\/(.*)$/) "newPath" + "$1");
} else if (request.uri.match(^\/path3\/.*$)) {
request.uri = request.uri.replace(/^\/path3\/(.*)$/) "newPath" + "$1");
}
I would say about 75% of the 300 if statements look similar. The path1, path2 examples are overly simplistic, the regex for each of these range from simple to complex. The other 25% incorporate other logic aside from just rewriting the URL. Think injecting headers, or in some cases one more level of nested if/else statements.
Here is an example of what some of those might look like:
else if (request.uri.match(^\/path3(-new)?\/.*$)) {
request.headers['header'] = {"value": "something"};
if (request.uri.match(^\/path3\/.*$)) {
request.uri = request.uri.replace(/^\/path3\/(.*)$/) "/newPathA/" + "$1");
} else if (request.uri.match(^\/path3-new\/.*$)) {
request.uri = request.uri.replace(/^\/path3-new\/(.*)$/) "/newPathB/" + "$1");
}
}
I went down the path to build functions to handle different things, like the uri replace lines. The challenge is the code that the functions are replacing aren't difficult, and it just feels like a bit of a waste. I can either have a line that does this:
request.uri = request.uri.replace(/^\/path3-new\/(.*)$/) "/newPathB/" + "$1");
Or I can have this:
function pathModifier(newPath) {
request.uri = reqUri.replace(/^(\/.*?[\/])(.*)$/, newPath);
return;
}
pathRewriter("/newPathA/" + "$1");
I am not saving a lot, and it makes it more difficult to read for the person I pass this on to.
So, what would you do?
I'd use a declarative approach for the majority of cases and still expose a way to extend it by passing a custom function.
This way you have low amounts of repetition in your code while still offering the needed flexibility.
Something like this:
const rewrites = {
'/path1/': ['/newPathA/'],
'/path2/': ['/newPathB/'],
'/path3/': ['/newPathC1/', { headers: { name: 'value' } }],
'/path3-new/': ['/newPathC2/', { headers: { name: 'value' } }],
'/path4': ['/newPathD', { headers: { name: 'value' }, callback: req => {
req.customProperty = 123
req.doCustomStuff()
} }]
}
function handleRewrites (req, rewrites) {
const [newPath, options] = Object.keys(rewrites)
.find(oldPath => req.uri.startsWith(oldPath)) ?? []
if (newPath) {
req.uri = newPath + req.uri.slice(oldPath.length)
if (options?.headers) Object.assign(req.headers, options.headers)
options?.callback?.(req)
}
}
Of course you are free to design the "domain-specific language" of how to declare the rewrites as it best fits your use case. I chose an object with old path as key and [newPath, optionalOptionsObject] as value because the majority of the time I'd expect there to be just an oldPath=>newPath transformation but it could be different of course.
If you would need for example actual regular expressions in some cases, you could of course change it and do something like an array where you then use values of the format [oldPathOrRegex, newPath, optionalOptionsObject]. You could then allow the easy string path but also allow regexes (depending on whether it is instanceof RegExp you'd apply one logic or the other, allowing you to specify both types of path selectors with little effort when declaring the rewrites).
There is of course still the thing that you would specify the common headers of several routes multiple times. One of the possible ways to allow specifying a (possibly even nested) group of rewrites with common properties that get merged with any per-route properties would be creating a function that takes the common settings plus another rewrite list and returns an expanded version of each given rewrite, and to then spread the result of the function with ...:
function withOptions (commonOptions, rewrites) {
for (const rewrite of Object.values(rewrites)) {
if (!rewrite[1]) rewrite[1] = {}
const rewriteOptions = rewrite[1]
if (commonOptions.headers || rewriteOptions.headers) {
rewriteOptions.headers = {
...commonOptions.headers,
...rewriteOptions.headers
}
}
if (commonOptions.callback || rewriteOptions.callback) {
const childCallback = rewriteOptions.callback
rewriteOptions.callback = req => {
commonOptions.callback?.(req)
childCallback?.(req)
}
}
}
return rewrites
}
const rewrites = {
'/path1/': ['/newPathA/'],
'/path2/': ['/newPathB/'],
...withOptions({ headers: { name: 'value' } }, {
'/path3': ['/newPathC2/'],
'/path3-new': ['/newPathC3/']
}),
'/path4': ['/newPathD', { headers: { name: 'value' }, callback: req => {
req.customProperty = 123
req.doCustomStuff()
} }]
}
Note that in this example I wrote withOptions so it mutates the given rewrites, because it was the simplest way and I assumed it would be passed object literals anyway. If this isn't true, it can be changed to return copies of course.
If you have many cases, I would suggest using a HashMap/Object, to map between the lookup key and their value.
const paths = {
'/old/path/to/a': '/new/path/to/a',
'/old/path/to/b': '/new/path/to/b',
'/old/path/to/c': '/new/path/to/c'
}
function getNewPath(oldPath) {
return paths[oldPath] || '/fallback/not/found'
}
Or if you have to handle different business logic, you could also return a function
const paths = {
'/old/path/to/a': () => { return modifyA() },
'/old/path/to/b': () => { return modifyB() },
'/old/path/to/c': () => { return modifyC() },
}
function getNewPath(oldPath) {
return paths[oldPath]()
}
Can someone explain why harvesters[i] would return undefined in this case? I've used similar code before with no issues. This is for the game Screeps.
var harvesters=_(Game.creeps).filter( { memory: { role: 'harvester' } } );
for(var i in harvesters)
{
//console.log(harvesters[i]); //this is the debug code I mention below
harvesters[i].memory.sourceid=0;
}
}
After some testing (thanks to the comments) I found that harvesters[i] did not return the harvester object I expected.... each harvester is apparently an instance of
function wrapperValue() {
return baseWrapperValue(this.__wrapped__, this.__actions__);
}
when I try outputting it to console. Why isnt this a creep object?
You're currently using lodash's chained sequence functionality, in order to extract the unwrapped value you'll need to call .value().
Your code will have to look somewhat like this:
const harvesters = _(Game.creeps).filter(
{
memory: {
role: 'harvester'
}
}
).value();
Alternatively you can use _.filter directly:
const harvesters = _.filter(Game.creeps, {
memory: {
role: 'harvester'
}
});
I want to perform more logic before writing an element to an array:
tempDatensatz.push( () => {
var current = window.dataForDataTable[i].outbounds[key].updatedAt;
if (current) {
return current.toString();
} else {
return "".toString();
}
});
Getting the value from that array will be achieved like this:
tempDatensatz[0]()
But I want the same logic in it without having a function to call. I need a normal array, where I get a value like this:
tempDatensatz[0]
What can I do instead?
Updated
I published my project to gitHub, you can take a look if you need a better understanding :)
https://github.com/te2020/GoEuro/blob/master/GoEuro/Views/Home/Index.cshtml
Use an immediately invoked function instead of just a function:
tempDatensatz.push( (function(){
var current = window.dataForDataTable[i].outbounds[key].updatedAt;
if (current) {
return current.toString();
} else {
return "".toString();
}
})());
The function will be executed immediatly after it definition, returning the result. So push won't push a reference to that function but instead it will push it returned value (the result).
You can write a proxy as follows:
function makeProxy(array) {
return new Proxy(array, {
get(target, property) {
return !isNaN(property) ? target[property]() : target[property];
}
});
}
const tempDatensatz = [];
const useThisOne = makeProxy(tempDatensatz);
useThisOne.push(() => alert("Hi, Jane!"));
useThisOne[0];
Pushing/writing to the array will work as expected, but retrieving its elements will go through the get handler, which will execute the function.
You could just use an expression, like:
tempDatensatz.push(
(window.dataForDataTable[i].outbounds[key].updatedAt || '').toString();
);
For more complex expressions you can often use the ternary operator. For the above that would look like this:
tempDatensatz.push(
window.dataForDataTable[i].outbounds[key].updatedAt
? window.dataForDataTable[i].outbounds[key].updatedAt.toString()
: ''
);
Your code
When looking at the github code you linked to, you can do all that pushing with this "oneliner":
var tempDatensatz =
['companyId', 'mode', 'duration', 'outboundId', 'journeyId', 'departureTime',
'arrivalTime', 'stops', 'price', 'updatedAt', 'segments']
.map( prop => (window.dataForDataTable[i].outbounds[key][prop] || '').toString() );
I am creating a module that takes in several complicated JSON files and would like some code to give the user feedback if certain elements are absent.
Below is the way I am doing it now, but I cannot help to think there must be a cleaner, less hacky way.
var _und = require("underscore");
//this function takes a list of required attributes and ensures they are present
var check_req_attr = function(config, req_attr, callback) {
var config_attr = Object.keys(config);
var absent_attr = _und.difference(req_attr, config_attr); //slightly hacky code that checks to ensure config has correct vars
if (absent_attr.length !== 0) {
throw Error("missing following attributes from config:" + absent_attr);
} else {
callback();
};
};
It just feels...dirty. If there is no real elegant way to do it, I would be open to critiques on my code. Thanks!
Parse the JSON to JS.
var data = JSON.parse(theJson);
Use something like:
function hasKey(obj, key) {
return typeof obj[key] !== 'undefined';
};
function hasKeys(obj, keys) {
for (var i = 1, len = keys.length; i < len; i++) {
if (!hasKey(obj, keys[i])) {
return false;
};
};
return true;
};
Now you can simply do:
if (hasKeys(data, ["firstKey", "secondKey", "thirdKey"]) {
console.log("valid");
};
This should be the way to do it, using every and has:
if (_und.every(req_attr, function(attr) {
return _und.has(config, attr);
}))
throw new Error();
In a native environment, you would just use the in operator:
req_attr.every(function(attr){ return attr in config; })
I think your solution is actually quite elegant! No need for an anonymous function, and the loop (which must happen at some point, obviously) neatly abstracted away with difference.
Two suggestions:
I'd give the function a synchronous signature. No callback argument. There can't be any reason to go async if you honor the function signature (i.e. basing your answer on config and req_attr only).
I'd change the function to return the missing properties (attributes is wrong term). You could also add a requireProperties function that uses this "check" function that would throw if a property was missing. This allows for different kind of uses.
Why don't you try with something like:
obj = JSON.parse(json);
and then check
if(obj.YourProperty == undefined){
//do something..
}
Hope i understood your question.. It should work with complicated JSON files too.. Good luck ;)
You could also use the in operator (requiredAttr in obj):
function objHasAllRequiredAttrs(obj, attrNames) {
return attrNames.reduce(function(memo, attrName) {
return memo && (attrName in obj);
}, true);
}
objHasAllRequiredAttrs({foo:1}, ['foo']); // => true
objHasAllRequiredAttrs({bar:1}, ['foo']); // => false
I have run into some trouble with a piece of backbone code. The code below relates to a render function. I can retrieve all the models. My trouble arises when I try to use the "Collections.where" method at line marked number #1. As you can see, I have passed an object literal into the render function but for some reason I am unable to reference it within the customers.where method on line #1. When I give this method a literal number like 45 it works. Is there some way around this so I can pass the variable reference in?
Thanks alot
render: function(options) {
var that = this;
if (options.id) {
var customers = new Customers();
customers.fetch({
success: function (customers) {
/* #1 --> */ var musketeers = customers.where({musketeerId: options.id});
console.log(musketeers.length) //doesn't work as options.id is failing on last line
var template = _.template($('#customer-list-template').html(), {
customers: customers.models
});
that.$el.html(template);
console.log(customers.models);
}
});
} else {
var template = _.template($('#customer-list-template').html(), {});
that.$el.html(template);
}
}
Although it isn't explicitly documented, Collection#where uses strict equality (===) when searching. From the fine source code:
where: function(attrs, first) {
if (_.isEmpty(attrs)) return first ? void 0 : [];
return this[first ? 'find' : 'filter'](function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
},
note the attrs[key] !== model.get(key) inside the callback function, that won't consider 10 (a probable id value) and '10' (a probable search value extracted from an <input>) to be a match. That means that:
customers.where({musketeerId: 10});
might find something whereas:
customers.where({musketeerId: '10'});
won't.
You can get around this sort of thing with parseInt:
// Way off where you extract values from the `<input>`...
options.id = parseInt($input.val(), 10);