Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6 destructuring preprocessing

Tags:

Problem

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.

Possible solutions

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().

Question

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.

like image 744
Charlie Avatar asked Jun 10 '17 12:06

Charlie


People also ask

What is ES6 Destructuring?

Destructuring means to break down a complex structure into simpler parts. With the syntax of destructuring, you can extract smaller fragments from objects and arrays. It can be used for assignments and declaration of a variable.

What is Destructure?

To destroy the structure of something. To dismantle.

How do you Destructure an object inside an array?

Destructuring in Arrays. To destructure an array in JavaScript, we use the square brackets [] to store the variable name which will be assigned to the name of the array storing the element. const [var1, var2, ...]

What does Destructuring do exactly when would you use it?

Destructuring is a JavaScript expression that allows us to extract data from arrays, objects, and maps and set them into new, distinct variables. Destructuring allows us to extract multiple properties, or items, from an array​ at a time.


2 Answers

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
like image 149
trincot Avatar answered Oct 04 '22 15:10

trincot


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);
}
like image 42
Estus Flask Avatar answered Oct 04 '22 16:10

Estus Flask