What would the best way to clean these if statements up? - javascript

I am working with a very simple if statement tree and want to write the code as DRY as possible, I believe I am implementing the DRYest code possible for my use case and have attempted to write in pointers to an object key (doesn't seem possible without setter function) and simplify the curly brackets but for purposes of the question I have left them in to make things clear on what needs to be done.
Is there a simpler version of this if tree?
let query = {};
if (min_budget || max_budget) {
if(min_budget && max_budget) {
query['budget.middleBound'] = { $gte: min_budget, $lte: max_budget }
} else if (min_budget && !max_budget) {
query['budget.middleBound'] = { $gte: min_budget }
} else if (max_budget && !min_budget) {
query['budget.middleBound'] = { $lte: max_budget }
}
}

Consolidating the various comments:
let query = {};
if (min_budget && max_budget) {
query['budget.middleBound'] = { $gte: min_budget, $lte: max_budget };
} else if (min_budget) { // ***
query['budget.middleBound'] = { $gte: min_budget };
} else if (max_budget) { // ***
query['budget.middleBound'] = { $lte: max_budget };
}
Barring more domain-specific information, that's probably the simple, direct version.
You could do it with just two ifs if you don't mind modifying an existing object:
let query = {};
if (min_budget || max_budget) {
const mb = query['budget.middleBound'] = {};
if (min_budget) {
mb.$gte = min_budget;
}
if (max_budget) {
mb.$lte = max_budget;
}
}

If you only care about syntax and short code, you could make use of the short-circuit evaluation.
let query = {};
let bound = (min_budget || max_budget) && (query['budget.middleBound'] = {});
min_budget && (bound.$gte = min_budget);
max_budget && (bound.$lte = max_budget);
The idea of the code is that we first create a new Object at query['budget.middleBound'] if we need to add either the min or max conditions.
We also save a reference in bound and use it further (so we don't have to access again the budget.middleBound property on query, which has a long name). We still only create one extra Object if needed.
Note that adding properties to Objects after you create them is slower than creating the Object with all the keys already present.
It's not usually recommended to write code like this in production, as it takes more time to understand what the code does when executed.

Related

Function returning "undefined" value? JS and Discord.js

Please excuse how utterly "noobish" I am, I'm trying to learn as I go along but I'm very new.
I have the below code which I'm trying to use for a Discord bot. For the most part it works, however the # "ping" simply returns "#undefined" as opposed to the values I've set in the consts.
Would anyone be so kind as to point me in the right direction on this?
const ping = {
roleID: function() {
return this.role;
}
}
const John = {
role:"abc"
}
const Mary = {
role:"123"
}
function pingSubbed() {
let pingID = message.author.username;
if (pingID == "John") {
ping.roleID.call(John);
}
if (pingID == "Mary") {
ping.roleID.call(Mary);
}
}
yield hook.send(`**${message.author.username}**\n` + " " + messageContents + " " + "#"+pingSubbed());
I'm expecting the function pingSubbed() to determine the username of the person who posts in Discord, for example John, reference the above ping.roleID.call(John) and then take the appropriate role (in this case John = abc) and sending this information as a message itself - #123 - as in the last line "#"+pingSubbed()
You might find a look up table simpler to author and maintain:
function pingSubbed() {
let getId = Function.call.bind(ping.roleID);
return {
John: getId(John),
Mary: getId(Mary),
}[message.author.username] || ("Unknown User:"+message.author.username);
}
This puts a lot less boilerplate (even no quotes) in the way of your raw logic.
Even more jedi would be to make an iterable object containing your users instead of free-floating variables (well const(s)). This drastically simplifies the already-simpler look up table method:
const ping = {
roleID: function() {
return this.role;
}
}
const users={
John : {
role:"abc"
},
Mary: {
role:"123"
}
}
function pingSubbed() {
return ping.roleID.call(users[message.author.username]) ||
("Unknown User:"+message.author.username);
}
that gets it to the point of being almost all data with minimal logic and code to worry about...
The call inside your Object is working well you just forget to return the value that you need from your function
function pingSubbed() {
let pingID = message.author.username;
if (pingID == "John") {
ping.roleID.call(John);
}
if (pingID == "Mary") {
ping.roleID.call(Mary);
}
return pingID // add this line
}
When you use this keyword inside an object, it refers to the object itself.

How to add keyword to acorn or esprima parser

I am working on a language that transpiles to javascript and has a similar syntax. However I want to include some new type of block statements. For syntax purposes they are the same as an IfStatement. How can I get esprima or acorn to parse this program MyStatement {a=1;} without throwing an error? Its fine if it calls it an IfStatement. I would prefer not to fork esprima.
It turns out, that the plugin capabilities of acorn are not really documented. It seems like forking acorn would be the easiest route. In this case, it is as simple as searching for occurances of _if and following a similar pattern for _MyStatement.
However it is possible to write a plugin to accomplish what I was trying to do. It seems a bit of a hack, but here is the code. The basic steps are:
To exend Parse and add to the list of keywords that will be recognized by the first pass
Create a TokenType for the new keyword and add it to the Parser.acorn.keywordTypes, extend parseStatement so that it processes the new TokenType
Create a handler for the new TokenType which will add information to the Abstract Syntax Tree as required by the keyword functionality and also consume tokens using commands like this.expect(tt.parenR) to eat a '(' or this.parseExpression() to process an entire expression.
Here is the code:
var program =
`
MyStatement {
MyStatement(true) {
MyStatement() {
var a = 1;
}
}
if (1) {
var c = 0;
}
}
`;
const acorn = require("acorn");
const Parser = acorn.Parser;
const tt = acorn.tokTypes; //used to access standard token types like "("
const TokenType = acorn.TokenType; //used to create new types of Tokens.
//add a new keyword to Acorn.
Parser.acorn.keywordTypes["MyStatement"] = new TokenType("MyStatement",{keyword: "MyStatement"});
//const isIdentifierStart = acorn.isIdentifierStart;
function wordsRegexp(words) {
return new RegExp("^(?:" + words.replace(/ /g, "|") + ")$")
}
var bruceware = function(Parser) {
return class extends Parser {
parse(program) {
console.log("hooking parse.");
//it appears it is necessary to add keywords here also.
var newKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this const class extends export import super";
newKeywords += " MyStatement";
this.keywords = wordsRegexp(newKeywords);
return(super.parse(program));
}
parseStatement(context, topLevel, exports) {
var starttype = this.type;
console.log("!!!hooking parseStatement", starttype);
if (starttype == Parser.acorn.keywordTypes["MyStatement"]) {
console.log("Parse MyStatement");
var node = this.startNode();
return this.parseMyStatement(node);
}
else {
return(super.parseStatement(context, topLevel, exports));
}
}
parseMyStatement(node) {
console.log("parse MyStatement");
this.next();
//In my language, MyStatement doesn't have to have a parameter. It could be called as `MyStatement { ... }`
if (this.type == tt.parenL) {
node.test = this.parseOptionalParenExpression();
}
else {
node.test = 0; //If there is no test, just make it 0 for now (note that this may break code generation later).
}
node.isMyStatement = true; //set a flag so we know that this if a "MyStatement" instead of an if statement.
//process the body of the block just like a normal if statement for now.
// allow function declarations in branches, but only in non-strict mode
node.consequent = this.parseStatement("if");
//node.alternate = this.eat(acornTypes["else"]) ? this.parseStatement("if") : null;
return this.finishNode(node, "IfStatement")
};
//In my language, MyStatement, optionally has a parameter. It can also by called as MyStatement() { ... }
parseOptionalParenExpression() {
this.expect(tt.parenL);
//see what type it is
console.log("Type: ", this.type);
//allow it to be blank.
var val = 0; //for now just make the condition 0. Note that this may break code generation later.
if (this.type == tt.parenR) {
this.expect(tt.parenR);
}
else {
val = this.parseExpression();
this.expect(tt.parenR);
}
return val
};
}
}
process.stdout.write('\033c'); //cls
var result2 = Parser.extend(bruceware).parse(program); //attempt to parse
console.log(JSON.stringify(result2,null,' ')); //show the results.

Null Check for destructured Variables in object

I have the code shown below for destructuring end_time property from this.props.auction object
const {auction: {auction: {end_time}}} = this.props;
But the issue here is above constant will be undefined if auction is an empty object. To fix this, I have changed the code to
if(Object.keys(this.props.auction).length) {
var {auction: {auction: {end_time}}} = this.props;
} else {
var {end_time} = "";
}
The above solution works but is ugly and I believe there is definitely a far better way of doing it.
Followed the answer from this post and
My attempt so far is:
const {auction: {auction: {end_time = null}}} = this.props || {};
I thought the above one will set end_time by default to null but I guess since auction is not defined it is throwing an error.
Please suggest a better way of declaring the end_time constant which takes care of an empty auction
You don’t need to use destructuring every time you can use it.
const auction = this.props.auction.auction;
const end_time = auction === undefined ? null : auction.end_time;
You could potentially use destructuring with default values like this:
const { auction: { auction: { end_time = null } = {} } = {} } = this.props || {};
But because the syntax above is cumbersome and unnatural to follow, I ultimately yield to Ry's advice in this case:
You don’t need to use destructuring every time you can use it.
I realize this is tagged ecmascript-6, but this question presents a great example where using the optional chaining operator and nullish coalescing operator seems the most natural solution, at least when they become officially merged into the ECMAScript 2020 specification:
const end_time = this.props?.auction?.auction?.end_time ?? null;
By combining the use of Optional chaining and Nullish Coalescing Operator you can achieve this goal with only one line like this:
const end_time = props.auction?.auction?.end_time ?? '';
Below are few testing functions to understand the concept:
const end_time_defaultValue = 'end_time_defaultValue';
function testWithEndTime() {
const props = {
auction: {
auction: {
end_time: new Date(),
kay1: 'value1',
kay2: 'value2'
}
}
};
const end_time = props.auction?.auction?.end_time ?? end_time_defaultValue;
console.log('testWithEndTime() => ', end_time);
}
testWithEndTime();
// outputs the today date
function testWithoutEndTime() {
const props = {
auction: {
auction: {
kay1: 'value1',
kay2: 'value2'
}
}
};
const end_time = props.auction?.auction?.end_time ?? end_time_defaultValue;
console.log('testWithoutEndTime() => ', end_time);
}
testWithoutEndTime();
// outputs the end_time_defaultValue
// because the key 'end_time' does not exist
function testWithoutAuctionAuction() {
const props = {
auction: {
}
};
const end_time = props.auction?.auction?.end_time ?? end_time_defaultValue;
console.log('testWithoutAuctionAuction() => ', end_time);
}
testWithoutAuctionAuction();
// outputs the end_time_defaultValue
// because the key 'auction.auction' does not exist
function testWithoutPropsAuction() {
const props = {};;
const end_time = props.auction?.auction?.end_time ?? end_time_defaultValue;
console.log('testWithoutPropsAuction() => ', end_time);
}
testWithoutPropsAuction();
// outputs the end_time_defaultValue
// because the key 'props.auction' does not exist
Be careful about browsers compatibility
But if you're using a framework like React, babel will do the job for you.
You may do de-structuring in 2 steps: first de-structure your props, then the necessary object that could be undefined at certain point in component life-cycle.
// instead of
const {auction: {auction: {end_time}}} = this.props;
// you may do
const { auction } = this.props;
let end_time;
if(auction){
({ end_time } = auction);
}

How to build a namespace-like string using chained variables

This is a strange one, but I'm exploring it to see if it's possible.
Let's say that I have a .NET application where I am using PubSub. I want a way to define the topic string using chained objects (not functions). The goal is to allow me a way of defining strings that lets me to take advantage of Visual Studio's IntelliSense and reduce the likelihood of spelling errors.
Here's an example:
/* Manual way */
var topic = "App.Navigation.CurrentItem"
/* Desired Solution */
// ... define the objects here ...
var topic = App.Navigation.CurrentItem;
console.log(topic); // "App.Navigation.CurrentItem"
var topic2 = App.Footer.CurrentItem;
console.log(topic2); // "App.Footer.CurrentItem"
I'd like each object to be responsible for outputing it's own value, and have the chaining process responsible for joining itself to the previous chained object via a predefined separator (in my case, a period [.]).
I've been playing with JavaScript getter syntax, but I'm curious if there's a better way.
Has anyone done something like this before, and if so, how did you solve it?
You're requirements aren't totally clear to me, but are you looking for something like this?
function namespace(ns) { this._ns = ns; }
namespace.prototype.toString = function() {return this._ns};
namespace.prototype.extend = function(suffix) {
return new namespace(this._ns + "." + suffix)
};
Usage:
App = new namespace('App');
App.Navigation = App.extend('Navigation');
App.Navigation.CurrentItem = App.Navigation.extend('CurrentItem');
console.log(App.Navigation.CurrentItem.toString()); // "App.Navigation.CurrentItem"
This is what I ended up with after reviewing StriplingWarrior's answer:
function Namespace(name, config) {
if (typeof name === "object") {
config = name;
name = null;
}
config = config || {};
this._ns = name;
this.define(config);
}
Namespace.prototype.toString = function() { return this._ns };
Namespace.prototype.define = function(config, base) {
base = base || this;
for (key in config) {
var name = (base._ns) ? base._ns + "." + key : key;
base[key] = new Namespace(name);
base.define(config[key], base[key]);
}
return base;
};
Usage:
var App = new Namespace("App", {
Navigation: {
Items: {
Current: {}
}
},
Content: {},
Footer: {
Items: {
Current: {}
}
}
});
console.log(App.toString()); // App
console.log(App.Navigation.Items.Current.toString()); // App.Navigation.Items.Current
console.log(App.Footer.toString()); // App.Footer
I also wrote a convenience method to reduce the toString():
function NS(namespace) {
return namespace.toString();
}
console.log(NS(App.Navigation.Items.Current));
Thanks again to StriplingWarrior for the the help!

What's best practice to ensure JavaScript objects are set and not undefined when handling multiple leveled objects?

I'm a js developer and work in an environment where we do API calls and get some data returned. The structure of the data returned is HIGHLY inconsistent so therefor we can't assume anything about the data returned.
Picture the following scenario:
$.ajax({
success: function(data){
// Now I want to access a property 4 levels in
var length = data.results.users.length;
doSomeStuffWithLength(length);
}
})
What's the correct way to ensure data.results.users.length is not undefined? Because of the inconsistencies from the API, each level of the returned object could be broken/undefined. Do I really have to do the following:
if (data && data.results && data.results.users && data.results.users.length){
var length = data.results.users.length;
doSomeStuffWithLength(length);
}
Aren't there more elegant solutions?
You can create helper function like this.
Expect object with structure like this :
var someObj = {
some: {
other: {
third: 'bingo',
qwe: 'test'
}
}
};
Would be great to have something like
getPropByPath(someObj, 'some.other.qwe');
So the implementation of getPropByPath may looks like following:
function getPropByPath(obj, path){
var parts = path.split('.'),
root = obj;
for(var i=0; i<parts.length; i++) {
if(root[parts[i]] !== 'undefined') {
root = root[parts[i]]
} else {
return false;
}
}
return root;
}
If at all levels there may be something undefined, you should check all levels, something like:
var length = data &&
data.results &&
data.results.users &&
data.results.users.length || 0;
You can also use some helper function. Here's one:
function getPropValue ( path, defaultIfNotExisting ) {
defaultIfNotExisting = defaultIfNotExisting || 0;
path = path.split('.'), test = this;
while (path.length) {
test = test[path.shift()];
if (!test) {
return defaultIfNotExisting;
}
}
return test;
}
// usage in your case:
if ( getPropValue.call(data, 'results.users', []).length) { /* do stuff */}

Categories