Node Invalid Left-Hand Side In Assignment Processing

On By In 1

After learning all about default parameters and rest & spread operators, let’s continue the syntactic sugar train in our Learning ES6 series with destructuring in ECMAScript 6.

TL;DR

Destructuring makes it easier to work with objects and arrays in JavaScript. Using a pattern syntax similar to object and array literals, we can poke into data structures and pick out the information we want into variables.

// object pattern matching let {lName, fName} = {fName: 'John', age: 15, lName: 'Doe'}; // array pattern matching let [first, second, third] = [8, 4, 100, -5, 20]; // output: Doe, John console.log(lName + ', '+ fName); // output: 100, 4, 8 console.log(third, second, first);

As you can see, we can store properties of an object or elements of an array using pattern matching. You can find many more examples in the destructuring code examples which are part of the Learning ES6 Github repo.

If you’re finding destructuring intriguing (or maybe confusing), keep on reading for a more in-depth explanation.

Some background

JavaScript developers spend quite a bit of time extracting data from arrays and objects. Destructuring allows variable binding via pattern matching with support for both arrays and objects. The patterns closely resemble those of array and object literals. Destructuring is also fail-soft, meaning that values not found return instead of an error.

But first, let’s take a look at how we deal with objects and arrays currently. There is a verbose way of defining objects and arrays:

var config = new Object(), myArray = new Array(); config.delay = 500; config.title = 'Hi!'; config.info = new Object(); // add to nested object config.info.name = 'Elijah'; myArray.push(1); myArray.push(new Array()); myArray.push(true); // add to nested array myArray[1].push('hello');

This can be significantly shortened into one-liners using literal syntax:

var config = {delay: 500, title: 'Hi!', info: {name: 'Elijah'}}, myArray = [1, ['hello'], true];

Similarly, there’s a verbose way of retrieving data from objects and arrays:

var config = {delay: 500, title: 'Hi!', info: {name: 'Elijah'}}, KEY = 'info', delay = config.delay, configTitle = config['title'], info = config[KEY], fullName = config.name, myArray = [1, ['hello'], true], first = myArray[0], second = myArray[1], secondNest = second[0], third = myArray[2];

ES6 provides a new syntax for quickly retrieving property values from objects and elements from arrays. This process is called destructuring assignment and we’ll spend the rest of our time looking at examples.

Object destructuring

Object destructuring assignment uses an object literal pattern on the left hand side of an assignment operation. Let’s convert the object portion of the ES5 example above using ES6 destructuring (and ):

let config = {delay: 500, title: 'Hi!', info: {name: 'Elijah'}}, {delay, info, title} = config; // output: {name: 'Elijah'}, 500, 'Hi!' console.log(info, delay, title);

We were able to store references to the 3 property values within into variables with names that matched the property keys of . This is actually the shorthand syntax for object destructuring. If instead we wanted to use different variable names, we can use the full syntax:

let config = { delay: 500, title: 'Hi!', info: {name: 'Elijah'} }, { info: one, title: two, empty: three, delay: four } = config; // output: {name: 'Elijah'}, 'Hi!', undefined, 500 // missing properties have `undefined` value console.log(one, two, three, four);

We were able to use an alias for the property from , naming it . We did the same for () and (). Notice that we also tried to get the property out of but since it doesn’t exist is . This works exactly how it would in ES5 with dot- or bracket-notation.

Nested object destructuring

Destructuring also supports nesting. So if we wanted to get the value of ( variable in the early example) we can do the following:

let config = {delay: 500, title: 'Hi!', info: {name: 'Elijah'}}, // `delay` is using shorthand syntax mixed in w/ // full syntax { info: {name: fullName}, delay, title: configTitle } = config; // output: 'Elijah', 'Hi!', 500 console.log(fullName, configTitle, delay);

The destructure pattern we specify in order to assign to is a nested object. If we were ok with the values of going into a variable named , we could’ve simply done: . It’s worth pointing out that you can not assign both parent and child properties at the same time. So in the example, you can either get the property or its child properties. Not both.

We learned earlier that destructuring is fail-soft so missing properties result in values. This doesn’t apply, however, when trying to assign a missing child property whose parent property is also missing:

let options = {}, // `delay` would be `undefined`, but trying // to assign to `name` is an error // since `options.info` is already `undefined` {delay, info: {name}} = options;

Computed values in object destructuring

Destructure patterns also support using computed values in the full syntax:

const KEY = 'empty'; let options = {delay:500, empty:true, title:'Hi!'}, {[KEY]: empty, delay, title} = options; // outputs: 'Hi!', 500, true console.log(title, delay, empty);

Object destructuring gotchas

You don’t have to exclusively use destructuring when declaring a variable via or (or if you’re still in to that sort of thing). You can use it for normal assignments to variables that have already been declared. However, when you do assignment-only object destructuring, you have to wrap the entire statement in parentheses:

let a, b = {}; // some code ( {a, b: b.count} = {a: 1, b: 2} ); // output: 1, {count: 2} console.log(a, b);

If you leave off the parenthesis you will get a . This is because without the parentheses the statement looks like an invalid code block to the JavaScript engine. You should also notice that in the assignment, we assigned directly to . Basically, anything that can accept an assignment can be used in a destructure pattern.

Array destructuring

Array destructuring works much the same way as object destructuring except you use an array literal destructure pattern for the left hand side assignment operation. As we saw earlier, the ES5 way of storing elements of an array into a variable can be pretty cumbersome. We can rewrite it in ES6 as such:

let myArray = [1, ['hello'], true], [first, second, third] = myArray; // output: 1, ['hello'], true console.log(first, second, third);

There’s only one syntax with array destructuring and that’s the shorthand syntax because the index that the variable will map to is implied by its order in the array literal destructure pattern.

Nested array destructuring

If instead we wanted to get the element within the array, we can use a nesting pattern similar to what we did with object destructuring:

let myArray = [1, ['hello'], true], [first, [secondNest], third] = myArray; // output: 1, 'hello', true console.log(first, secondNest, third);

Skipping indices in array destructuring

Even though the array literal destructure pattern is order-dependent, we can still pick and choose which elements of the array we want stored as variables by skipping indices:

let sequence = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34], [first, , third, fourth, , , seventh] = sequence ; // output: 0, 1, 2, 8 console.log(first, third, fourth, seventh);

Pretty cool, huh?

Mixed object & array destructuring

As you might have already guessed, we can mix object and array literal destructure patterns. This can come in very handy when we’re pulling out various pieces of data from a JSON object because we no longer have to navigate the entire structure.

let json = { shapes: ['circle', 'square', 'triangle'], colors: 5, fill: true, author: { firstName: 'Ben', lastName: 'Ilegbodu', city: 'Pittsburg' } }, { fill, author: {lastName, firstName, city}, shapes: [, secondShape], colors: numColors } = json; // output: true, square, 5 console.log(fill, secondShape, numColors); // output: Ilegbodu, Ben, Pittsburg console.log(lastName, firstName, city);

Now that’s powerful stuff.

Destructuring use cases

The mixed object and array destructuring with JSON data is a real-world use case. Here are a few more.

Swapping values

What happens if we need to swap values in two variables? In the standard answer, we end up needing a third variable:

var a = 1, b = 2, temp; temp = a; a = b; b = temp;

However, now that we have destructuring, we can use array destructuring to quickly swap two numbers without the need for a variable:

let a = 1, b = 2; [b, a] = [a, b];

See what we did there? We constructed an array using the array literal syntax with two elements: and . Then using array destructuring we assigned the first element of the newly created array into and the second element into . The result is that the variables’ values have swapped. Now technically we did still create a temporary array, but we never had to store it into a variable.

Destructuring class objects

Class objects are similar to object literals except they are created using . As long as properties of the class object are accessible via dot- or bracket-notation, they will also be available via object destructuring. Here’s an example using the object:

let { protocol: scheme, host: domain, pathname: path, search: query, hash, href: url } = location; // output: true console.log( (scheme + '//' + domain + path + query + hash) == url );

Destructuring return values

Many methods on JavaScript APIs return arrays (such as on objects or on objects). You can use array destructuring to get the array values into variables quickly:

let [, areaCode, exchange, lineNumber] = /^(\d\d\d)-(\d\d\d)-(\d\d\d\d)$/ .exec('650-555-1234'); // output: 650, 555, 1234 console.log(areaCode, exchange, lineNumber);

As we know, returns an array of the matching groups with the first element being the entire match. We’re not interested in that first piece, so we skip it and get the remaining three parts. You can imagine what this code looks like in ES5.

Handling multiple return values

Sometimes a function needs to return multiple pieces of data. This typically packaged together in an object literal or as an array tuple. Once again we can use destructuring to easily get out the data we care about.

Let’s say we have a method that takes as parameters an array and a string , where it finds the first string in that contains .

function find(list, token) { for (let i = 0; i < list.length; i++) { if (list[i].indexOf(token) > -1) return {index: i, val: list[i]}; } // match not found return {index: -1, val: undefined}; }

Now a caller of might care about the index at which was found or it might care about the string itself. That’s why returns an object literal containing both the number and the string.

If the caller needs a reference to both pieces of information, they can do:

let fruits = ['apple', 'grape', 'peach', 'pear'], {index, val} = find(fruits, 'ape');

Or if they just care about the :

let fruits = ['apple', 'grape', 'peach', 'pear'], {val} = find(fruits, 'ape');

Or if they care just about the :

let fruits = ['apple', 'grape', 'peach', 'pear'], {index} = find(fruits, 'ape');

Array destructuring & rest operator

We previously learned about the rest operator in the context of a function header. But it doesn’t only work with functions. It can also be used to simplify array manipulation.

For example we can turn this in ES5:

// ES5 var list = [9, 8, 7, 6, 5], first = list[0], second = list[1], rest = list.slice(2); // output: [7, 6, 5], 8, 9 console.log(rest, second, first);

To this in ES6 using the rest operator with array destructuring:

// ES6 let list = [9, 8, 7, 6, 5], [first, second, ...rest] = list; // output: [7, 6, 5], 8, 9 console.log(rest, second, first);

We can replace the call as well as the individual array indexing into a simple destructure pattern.

Destructured parameters

We can actually using destructuring in function headers too. Let’s pretend we needed to implement an method that takes the URL endpoint as well as a bucket of configuration options. In ES5, this would look something like:

// ES5 function ajax(url, options) { var method = options.method, delay = options.delay, callback = options.callback; console.log(url, method, delay); setTimeout( function() { callback('DONE!'); }, delay ); } ajax( 'https://api.eventbrite.com/get', { delay: 2000, method: 'POST', callback: function(message) { console.log(message); } } );

As you can see, we are trying to get values out of the object to use in our method. Just looking at the method, it’s not immediately clear what properties can be in . Documentation would be needed. The reason the object is used instead of individual parameters is to not explode the arity of the function and because some of the properties of could be optional. It’s also how named parameters are accomplished in ES5.

In ES6, there is still no official approach to named parameters, but we can get a bit closer using object destructuring of function parameters:

// ES6 function ajax(url, {method, delay, callback}) { // `method`, `delay` & `callback` are // destructured variables console.log(url, method, delay); setTimeout( () => callback('DONE!'), delay ); } ajax( 'https://api.eventbrite.com/get', { delay: 2000, method: 'POST', callback: function(message) { console.log(message); } } );

No more need to declare and assign the individual , and variables.

What if we want to support being optional? Well in ES5 the implementation would have to start with:

// ES5 function ajax(url, options) { // default `options` to empty object // so var declarations don't throw error // if `options` is `undefined` options = options || {}; // var declarations and // rest of the function... }

Well guess what? We can use default parameters to simply default to the empty object in ES6:

// ES6 function ajax(url, {method, delay, callback}={}) { // default {} is used to allow // object to be unspecified w/o // causing an error // rest of the function }

See what we did there? We combined default values with parameter destructuring. If is left unspecified or , it’ll trigger the default value of which then gets destructured into , and . If we didn’t provide a default value and didn’t specify in the call to , we would get an error because we’re trying to destructure . It’s good practice to specify a default value for object destructured parameters.

Ok, what if we wanted to have default values for and ? They are within . In ES5, we’d do something like:

// ES5 function ajax(url, options) { options = options || {}; // default the values of `method` and // `delay` var method = options.method || 'GET', delay = options.delay || 1000, callback = options.callback; // rest of the function... }

In ES6, we can use default values within our object destructure pattern to accomplish the property defaulting:

// ES6 function ajax(url, {method='GET', delay=1000, callback} = {}) { // default values w/in destructure pattern // rest of the function }

Nested default values! Cool, huh? That’s the power of ES6.

JavaScript engine support

According to the ECMAScript 6 Compatibility table, the following JavaScript engines support destructuring:

  • Babel
  • Traceur
  • TypeScript
  • Firefox (does support default values in destructure patterns yet)
  • Safari (partial support)

It’s surprising to see that Chrome doesn’t yet support destructuring. Neither does Node.js. This is also the first feature we’ve covered that Microsoft’s Edge doesn’t support either. My guess is that this feature may be low priority for these vendors since it’s basically 100% syntactic sugar. But it’s such a great feature!

Additional resources

You can check out the Learning ES6 examples page for the Learning ES6 Github repo where you will find all of the code used in this article running natively in the browser (for those that support destructuring). There are also examples running through Babel and Traceur transpilation.

You can also practice everything you’ve learned on ES6 Katas. It uses a TDD (test-driven development) approach for you to implement ES6 features such that all of the tests pass. I highly recommend it!

Other super helpful resources:

Coming up next…

So far in the Learning ES6 series we’ve looked at only syntactic sugar features in ECMAScript 6. But ES6 isn’t solely syntactic sugar. There’s actually new functionality as well. Next time we’ll take a look at the new operator. Until then…

FYI

This Learning ES6 series is actually a cross-posting of a series with the same name on my personal blog, benmvp.com. The content is pretty much the exact same except that this series will have additional information on how we are specifically leveraging ES6 here in Eventbrite Engineering when applicable. I’ll also be tackling the features in a different order than I did in my personal blog. The original destructuring blog post can be found here.

Tagged es6, javascript, learning-es6

Next on the list in our extensive JavaScript Error Handling series we’re going to examine the Invalid Left-Hand Assignment error in greater detail. The Invalid Left-Hand Assignment error is a sub-object of ReferenceError and is thrown, as the name implies, when code attempts to perform an invalid assignment somewhere.

In this post we’ll look at a few code examples to illustrate some common methods of producing an Invalid Left-Hand Assignment error, as well as examine how to handle this error when it rears its ugly head. Let the party begin!

The Technical Rundown

  • All JavaScript error objects are descendants of the Error object, or an inherited object therein.
  • The ReferenceError object is inherited from the Error object.
  • The Invalid Left-Hand Assignment error is a specific type of ReferenceError object.

When Should You Use It?

As one of the simplest JavaScript errors to understand, the Invalid Left-Hand Assignment error appears in only a handful of situations in which code is attempting to pass an assignment incorrectly. While this is generally thought of as a syntactic issue, JavaScript defines this particular assignment error as a ReferenceError, since the engine effectively assumes an assignment to a non-referenced variable is being attempted.

The most common example of an Invalid Left-Hand Assignment error is when attempting to compare a value using a assignment operator (=), rather than using a proper comparison operator (== or ===). For example, here we’re attempting to perform a basic comparison of the variable name with the values John or Fred. Unfortunately, we’ve made the mistake of using the assignment operator =, instead of a comparison operator such as == or ===:

Sure enough, rather than giving us an output, the JavaScript engine produces the expected Invalid Left-Hand Assignment error:

It’s worth noting that catching an Invalid Left-Hand Assignment error with a typical try-catch block is particular difficult, because the engine parses the code from inside out, meaning inner code blocks are parsed and executed before outer blocks. Since the issue of using a = assignment operator instead of a == comparison operator means the actual structure of the code is changed from the expected, the outer try-catch fails to be parsed and properly executed. In short, this means Invalid Left-Hand Assignment errors are always “raw”, without any simple means of catching them.

Another common method for producing an Invalid Left-Hand Assignment error is when attempting to concatenate a string value onto a variable using the addition assignment += operator, instead of the concatenation operator +. For example, below we’re attempting to perform concatenation on the name variable on multiple lines, but we’ve accidentally used the += operator:

This isn’t the syntax JavaScript expects when concatenating multiple values onto a string, so an Invalid Left-Hand Assignment error is thrown:

To resolve this, we simply need to replace += with the concatenation operator +:

Now we skip the Invalid Left-Hand Assignment error entirely and get our expected output indicating the full name stored in the name variable:

To dive even deeper into understanding how your applications deal with JavaScript Errors, check out the revolutionary Airbrake JavaScript error tracking tool for real-time alerts and instantaneous insight into what went wrong with your JavaScript code.

Related

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

varprintError=function(error,explicit){

    console.log(`[${explicit?'EXPLICIT':'INEXPLICIT'}]${error.name}:${error.message}`);

}

 

try{

    varname='Bob';

    if(name='John'||name='Fred'){

        console.log(`${name}returns!`)

    }else{

        console.log(`Just${name}thistime.`)

    }

}catch(e){

    if(einstanceofReferenceError){

        printError(e,true);

    }else{

        printError(e,false);

    }

}

1

Uncaught ReferenceError:Invalid left-hand side inassignment

varprintError=function(error,explicit){

    console.log(`[${explicit?'EXPLICIT':'INEXPLICIT'}]${error.name}:${error.message}`);

}

 

try{

    varname='Bob'

    +=' Smith';

 

    console.log(`Nameis${name}.`);

}catch(e){

    if(einstanceofReferenceError){

        printError(e,true);

    }else{

        printError(e,false);

    }

}

1

Uncaught ReferenceError:Invalid left-hand side inassignment

varprintError=function(error,explicit){

    console.log(`[${explicit?'EXPLICIT':'INEXPLICIT'}]${error.name}:${error.message}`);

}

 

try{

    varname='Bob'

    +' Smith';

 

    console.log(`Nameis${name}.`);

}catch(e){

    if(einstanceofReferenceError){

        printError(e,true);

    }else{

        printError(e,false);

    }

}

0 comments

Leave a Reply

Your email address will not be published. Required fields are marked *