Wish to enact upon arguments but with defaults defined
In my quest for self-documenting code with destructuring but being DRY wanting to do this...
async function shampoo({ lather = true, rinse = true, repeat = 2 } = {}) {
await api.dogWasherMachine(magicalArguments) // ???
// don't want to have to do this:
// api.dogWasherMachine({ lather, rinse, repeat })
// currenty arguments is {} and actual input is defined
}
How do I get these magical arguments that are defined?
arguments do not have the default input defined but how can I do this?
It's not possible to do it in the parameters alone - destructuring necessarily extracts each property into an independent named variable, without leaving a reference to the original object behind. You'll have to do it with another statement, eg:
async function shampoo(param = {}) {
const defaultObj = {
lather: true,
rinse: true,
repeat: 2
};
await api.dogWasherMachine({ ...defaultObj, ...param });
}
I use destructuring assignment to to self document inheritable classes that are used as interfaces. On construction I get all of the benefits of intellisense and when calling an API everything stays nice and DRY.
class washable {
constructor({ lather = true, rinse = true, repeat = 2 } = {}) {
this.lather = lather
this.rinse = rinse
this.repeat = repeat
}
}
class dog extends washable {
async shampoo() {
await api.dogWasherMachine(this)
}
}
Related
Let's assume that I've the following object:
let filters = {
brands: { ... },
price: { ... },
sizes: { ... },
...
}
The properties of the filters object will be set by the users. Which means sometimes the filters object may contain just brands, sometimes it may contain brands & price and so on.
I've written the following function to extract a specific property from the filters object:
let extractProperty = (propertyName) => {
({ propertyName, ...rest } = filters); // <-- propertyName isn't working here
console.log(propertyName);
}
extractProperty("brands");
If I invoke the above function, the console displays undefined.
Can anyone please point me out what I'm missing here?
CodePen Link
Note:
I've already resolved this issue using lodash.omit method. But I'm still curious to know why function parameter value isn't working in object-destructuring.
Not Duplicate:
This question is about passing default value
That code is looking for a property called propertyName, literally. To use the value in propertyName as the property name, you need to use computed notation, and you'll need to specify where to put the property's value. For instance, to put it in an existing example variable:
let extractProperty = (propertyName) => {
({ [propertyName]: example, ...rest } = filters);
// ^−−−−−−−−−−−−^^^^^^^^^^
console.log(example);
};
extractProperty("brands");
Your code is written assuming that rest already exists, but I suspect you really want to declare it locally, along with the variable to receive the property value:
let extractProperty = (propertyName) => {
const { [propertyName]: example, ...rest } = filters;
console.log(example);
};
extractProperty("brands");
Without const, let (or var, but var is deprecated), unless you have rest declared in an enclosing scope, that code will either fail with a ReferenceError (in strict mode) or fall prey to what I call The Horror of Implicit Globals (in loose mode), automatically creating a global variable.
why use destructuring here when you just want to get a property?
let filters = {
brands: { value:'b' },
price: { value:'p' },
sizes: { value:'s' },
}
let extractProperty = propertyName =>
console.log(filters[propertyName])
extractProperty("brands");
I don't know JS/ES6 well enough to describe my question in code. So most of this question is conceptually and in pseudo code.
Say I have a Contractor class like this:
class Contractor {
constructor(jobFn) {
// save jobFn;
}
dailyRoutine() {
// let result = DriveToWork()
const result = 6
DoTheJob(result)
DriveBackHome()
}
}
The problem is, what the DoTheJob() does might be different things in different places.
So in place A, it could be
he = new Contractor(write_front_end(with, this, and that))
And in place B, it could be
he = new Contractor(fix_backend_node(with, express))
I.e., the behavior need to be passed in during the constructor, and the action might need to take different kind and different amount of parameters.
Would such thing be possible with ES6?
Please show ES6 code that can pass function with different kind and different amount of parameters through the constructor to DoTheJob().
Further, the challenge is that the jobFn need to be a Curried function, meaning there is one or more parameter missing to do the DoTheJob job. Say if the jobFn is passed with Curried add(3), then DoTheJob will do UncurriedAdd of add(3, 6); if then jobFn is passed with Curried multiple(5), then DoTheJob will do Uncurried of multiple(5, 6);
Just assign the passed function to this.DoTheJob, and then call this.DoTheJob inside dailyRoutine:
class Contractor {
constructor(jobFn) {
this.DoTheJob = jobFn;
}
dailyRoutine() {
// DriveToWork()
this.DoTheJob();
// DriveBackHome()
}
}
const c1 = new Contractor(() => console.log('doing job A'));
c1.dailyRoutine();
const c2 = new Contractor(() => console.log('doing job B'));
c2.dailyRoutine();
// c1 again:
c1.dailyRoutine();
// feel free to reference any in-scope variables in the passed function,
// no need to pass the variables as additional parameters
const data = 'data';
const c3 = new Contractor(() => console.log('data is', data));
c3.dailyRoutine();
If dailyRoutine needs to be invoked with data that needs to be sent to the passed doTheJob function, just define the needed arguments in the function you pass, there's no need for actual currying here:
class Contractor {
constructor(jobFn) {
this.DoTheJob = jobFn;
}
dailyRoutine(doJobArg) {
this.DoTheJob(doJobArg);
}
}
// feel free to reference any in-scope variables in the passed function,
// no need to pass the variables as additional parameters
const data = 'data';
const c3 = new Contractor((arg) => console.log('data is', data, 'and arg is', arg));
c3.dailyRoutine('argDoTheJobIsCalledWith');
In my case, I may advise you that it's better to give the predicate to dailyRoutine, because this way you'll be able to reuse the same instance and give different predicates.
Anyway, there's a pure OOP solution for this, using method polymorphism, the JavaScript way (aka duck typing):
class Contractor {
driveBackHome() {}
dailyRoutine() {
const result = 6
this.doTheJob(result)
this.driveBackHome()
}
}
class SpecializedContractorA extends Contractor {
doTheJob(result) {
console.log('aaaaaaaaaaaaaaaaaaaaa', result)
}
}
class SpecializedContractorB extends Contractor {
doTheJob(result) {
console.log('bbbbbbbbbbbbbbbb', result)
}
}
const a = new SpecializedContractorA()
a.dailyRoutine()
const b = new SpecializedContractorB()
b.dailyRoutine()
flow 0.67.1 (but behavior continues to exist in 0.73.1)
Example:
type PropOptional = {
prop?: ComplexType
};
type ComplexType = {
callable: () => void,
anotherCallable: () => void
};
function usePropOptional(arg1: PropOptional) {
if (arg1.prop) {
arg1.prop.callable();
arg1.prop.anotherCallable();
arg1.prop.callable();
}
};
The function checks for the presence of arg1.prop before accessing any properties on arg1.prop. This should be sufficient to verify that arg1.prop is defined.
Flow is fine with the first time an arg1.prop property is accessed, which is the call to arg1.prop.callable() on the first line inside the if block. However, flow generates errors on subsequent attempts to access arg1.prop properties in the exact same if block:
arg1.prop.anotherCallable();
arg1.prop.callable();
I am forced to either prepend each line with a rote arg1.prop && truthy check, or reassign arg1.prop to a local variable inside the if block:
function usePropOptional(arg1: PropOptional) {
if (arg1.prop) {
const reallyExists = arg1.prop;
reallyExists.callable();
reallyExists.anotherCallable();
reallyExists.callable();
}
};
This doesn't feel right. What am I doing wrong or missing?
You can check this in a flow repl here on flow.org.
This is documented in FlowType's Type Refinement section:
Refinement Invalidations
It is also possible to invalidate refinements, for example:
// #flow
function otherMethod() { /* ... */ }
function method(value: { prop?: string }) {
if (value.prop) {
otherMethod();
// $ExpectError
value.prop.charAt(0);
}
}
The reason for this is that we don’t know that otherMethod() hasn’t
done something to our value.
...
There’s a straightforward way to get around this. Store the value
before calling another method and use the stored value instead. This
way you can prevent the refinement from invalidating.
// #flow
function otherMethod() { /* ... */ }
function method(value: { prop?: string }) {
if (value.prop) {
var prop = value.prop;
otherMethod();
prop.charAt(0);
}
}
So the workaround in your final case appears to be the suggested way to avoid this problem.
My code:
class DoIt {
city () {
// others operations...
const number = 5; // from operations
const amount = this.getAmount (number);
// others operations...
}
street () {
// others operations...
const number = 10; // from operations
const amount = this.getAmount (number);
// others operations...
}
getAmount (number) {
return number * 10 * 3.14 / 3; // example...
}
}
I use "eslint" to check my code and I have error:
error Expected 'this' to be used by class method 'getAmount'
class-methods-use-this
So where to put a method without "this"? This code is important for my class, so I shouldn't create helper class...
Maybe this method is not important? In OOP in other languages can exists method without 'this' in class methods?
class-methods-use-this is an eslint check to see if your class method does not refer to this. In some cases, it may be better to make it static by adding the static keyword in front of the method declaration. In other cases, you can just ignore this check.
https://eslint.org/docs/rules/class-methods-use-this
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.