Reputation: 830
Function arguments destructuring is an amazing feature in ES6.
Assume we want a function
named f
to accept an Object
which has an a
key
function f({ a }) {
return a;
}
We have default values for the case when parameter wasn't provided to a function in order to avoid Type Error
function f({ a } = {}) {
return a;
}
This will help in the following case
const a = f(); // undefined
Though, it will fail on
const a = f(null); // TypeError: Cannot match against 'undefined' or 'null'.
You can see how Babel transpiles the function to ES5 here.
It can be avoided by arguments validation and preprocessing. In Python
I could use decorator, but in JS we don't have them standardized, so it's not a good idea to use them yet.
Though, assume we have a decorator checkInputObject
, which makes needed checks and provides default values using given items list (or tree for the case of nested destructuring).
We could use it in the following way without @
notation
const f = checkInputObject(['a'])(({ a }) => a);
It could look like this with @
notation
@checkInputObject(['a'])
function f({ a }) {
return a;
}
Also I can make all needed actions in the function itself and only then use destructuring, but in this case I lose all advantages of function arguments destructuring (I'll not use it at all)
function f(param) {
if (!param) {
return;
}
const { a } = param;
return a;
}
I can even implement some common function like checkInputObject
in order to use it inside of the function
const fChecker = checkInputObject(['a']);
function f(param) {
const { a } = fChecker(param);
return a;
}
Though, using of additional code doesn't look elegant to me. I'd like to have not existent entities be decomposed to undefined
.
Assume we have
function f({a: [, c]) {
return c;
}
It would be nice to get undefined
in the case of f()
.
Do you know any elegant and convenient way to make [nested] destructuring resistant to nonexistent nested keys?
My concern is following: seems like this feature is unsafe for using in public methods and I need to make a validation by myself before using it. That's why it seems useless until used in private methods.
Upvotes: 5
Views: 1407
Reputation: 222493
The proper way to handle this in ES6 is to respect the way params are being processed by the language and shape API to fit it better, not in the opposite way.
The semantics behind destructured argument in fn(undefined)
is that the argument will be replaced with default value, in the case of fn(null)
means that nully argument won't be replaced with default value.
If data comes from the outside and should be conditioned/preprocessed/validated, this should be handled explicitly, not by destructuring:
function fn({ foo, bar}) { ... }
fn(processData(data));
or
function fn(data) {
const { foo, bar } = processData(data);
...
}
fn(data);
The place where processData
is supposed to be called is totally on developer's discretion.
Since proposed ECMAScript decorators are just helper functions with specific signature, same helper function can be used both with @
syntax and usual function calls, depending on the project.
function processDataDecorator(target, key) {
const origFn = target[key];
return target[key] = (...args) => origFn.apply(target, processData(...args));
}
class Foo {
constructor() {
this.method = processDataDecorator(this, 'method');
}
method(data) {...}
}
class Bar {
@processDataDecorator
method(data) {...}
}
And the way that the language offers since ES5 for default property values is Object.assign
. It doesn't matters if data
is undefined
, null
or other primitive, the result will always be an object:
function processData(data) {
return Object.assign({ foo: ..., bar: ...}, data);
}
Upvotes: 2
Reputation: 350272
You could use try...catch
and use that in a generic decorator function:
function safe(f) {
return function (...args) {
try {
return f(...args);
} catch (e) {}; // return undefined
};
}
function f({ a } = {}) {
return a;
}
f = safe(f);
console.log(f(null)); // -> undefined
console.log(f( { a:3 } )); // -> 3
Upvotes: 3
Reputation: 10906
I believe i can help you solve this problem by showing you a different way to look at it.
The problem you are facing is not about destructuring, because you will face it in any function that you are sending it different types that its expecting.
to solve such issue, ex. de-structuring, you can use type checking solution, such as Flow, which statically checks for types, hence it will fail at build time whenever you are sending wrong things to the function.
the essence of javascript is that its a dynamically types language, hence you might want the function to be able to work with any parameter sent to it, in this case, you can use dynamic type checking, the solutions are many.
the function parameters are more of an "agreement" to how you use the function, rather than accessing its parameters (after all you can just use arguments
to access them), hence by sending different things you are violating this agreement, which isn't a good practice anyways, its better to have 1 function that does 1 thing, and accepts as few complicated parameters as possible.
Upvotes: 2
Reputation: 856
didn't you just answer yourself the question you ask?
I think default param is more elegant way for this.
function f({a: [,c]}={a:[undefined,undefined]}) {
return c;
}
f();
Upvotes: -3