ES6 Destructuring Recipes

Datetime:2016-08-22 21:36:42          Topic: ECMAScript           Share

ES6 Destructuring is awesome, but it’s not always obvious how you can use it to its most awesome extent. In this post, I’ll show you just some of the ways you can make maximum use of destructuring to make your code terser and cleaner.

General Destructuring Recipes

Destructure the results of promise.all

Promise.all accepts an array of resolved promises, and provides the results through its then() function, also in the form of an array. This array can be destructured, providing easy access to each resolved promise.

Note: to see the results of the resolved promises, check the console log.

function welcomeAsync(avenger){
	return new Promise((resolve, reject) => { resolve ('hello ' + avenger) });
}

const avengers = [
	'ironMan',
	'captainAmerica',
	'blackWidow'
];
    
Promise
	.all(avengers.map(welcomeAsync))
	.then(([ironManWelcome, captainAmericaWelcome, blackWidowWelcome]) => {
		// Check the console to see the output
		console.log('************* The result of Promise.all *************');
  		console.log(ironManWelcome, captainAmericaWelcome, blackWidowWelcome);
    });

Swap values of two variables

File this under super-cool! Swap the values of two variables using destructuring in just one line:

let bruceBanner = 'Bruce Banner';
let theHulk = 'The Hulk';

// bruceBanner = 'Bruce Banner'
// theHulk = 'The Hulk'

// *** Here's the magic! ***
[bruceBanner, theHulk] = [theHulk, bruceBanner];

Pluck all properties by keyname from an array of objects

const avengers = [
  { heroName: 'ironMan' },
  { heroName: 'captainAmerica' },
  { heroName: 'blackWidow' },
];

const heroNames = avengers.map( ({ heroName }) => heroName );

// Output:
heroNames;

Hat-tip to Mikael Brevik

Get the value of an object when you don’t know the name of its property

There are times when you don’t know the name of an object’s property, but you still need its value nonetheless. For example, you might have a data feed formatted as an object, with a set of dynamically-generated ids as its keys.

In cases like this, you can still use destructuring to get the values of the object’s keys – you just you will need a little more ES6 magic in the form of Computed Object Properties :

// Setup our datafeed as an object with arbitrary ids as keynames
const datafeed = {
    id_876: 'value1',
    id_094: 'value2',
    id_233: 'value3'
}

// This is where we'll destructure our feed
const getValue = (key, feed) => {
    // In this function, we have no idea what the value of 'key' will be,
    // so we can't use it to destructure feed.

    // But we can turn 'key' into a Computed Object Property by wrapping square brackets
    // around it. Now, whatever value for 'key' is passed in will be turned into
    // an object property name, which we can use to destructure feed:
    const {
        [key]: value
    } = feed;

    return value;
}

// Get all the feed object's keys, then map their associated values 
// to an array. 
const output = Object.keys(datafeed).map(key => getValue(key, datafeed));

// Output (all the values, without once having to know the name of a key):
output;

Iterate over an array of objects, extracting properties from each object

const avengers = [{
    realName: 'Tony Stark',
    heroName: 'ironMan'
}, {
    realName: 'Steve Rogers',
    heroName: 'Captain America'
}, {
    realName: 'Natasha Romanov',
    heroName: 'Black Widow'

}];

let destructuredAvengers = [];

for (const {
      realName,
      heroName
   } of avengers) {
      destructuredAvengers.push(realName, heroName);
};

// Output:
destructuredAvengers;

Iterate through an array of arrays

Destructuring makes iterating through an array of arrays simple, which in turn makes flattening an array of arrays simple, too:

const avengers = [
   ['Tony Stark', 'ironMan'], 
   ['Steve Rogers', 'Captain America'],
   ['Natasha Romanov', 'Black Widow']
];

function flattenAvengers(avengers) {
	let flattenedAvengers = [];
	avengers.forEach(
                // destructure each [realName, heroName] array item from avengers array
		([realName, heroName]) => flattenedAvengers.push(realName, heroName)
	);
	return flattenedAvengers;
}

flattenAvengers(avengers);

Return item and index from array

Sometimes it’s not enough to return an item from an array – you need its index as well. Here’s how to do it with destructuring:

const avengers = [{
    realName: 'Tony Stark',
    heroName: 'ironMan'
}, {
    realName: 'Steve Rogers',
    heroName: 'Captain America'
}, {
    realName: 'Natasha Romanov',
    heroName: 'Black Widow'

}];

function findAvenger(realName, avengers) {
	for (let [index, avenger] of avengers.entries()) {
            if (avenger.realName === realName) {
                return { avenger, index };
            }
        }
        // Default values if we can't find our avenger:
        return { avenger: undefined, index: -1 }; 
}

// Output:
findAvenger('Natasha Romanov', avengers);

Hat tip to Axel Rauschmayer

Solution-specific Destructuring Recipes

Parse a URL in two lines

const url = ("http://www.example.com/folder/file.html");
[fullUrl, protocol, fullHost, fullPath] 
   = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);

// fullUrl: http://www.example.com/folder/file.html
// protocol: http
// fullHost: www.example.com
// fullPath: folder/file.html

(Hat-tip to Mozilla )

Parse complex data feeds in a single for..of loop

If you’re working with complex data (such as JSON returned from data feeds), accessing the info you need can be a royal pain if it’s nested deeply. Of course destructuring can help with this if its a single object, but what if you have multiple objects, and you want to pluck an item that’s nested deeply from each item?

Enter for..of , which will also handily accommodate destructuring:

// Setup complex data feed
const avengers = [{
    character: {
        heroName: 'Iron Man',
        realName: 'Tony Stark',
        partner: {
            realName: 'James Brodie',
            heroName: 'War Machine'
        }
    }
}, {
    character: {
        heroName: 'Captain America',
        realName: 'Steve Rogers',
        partner: {
            realName: 'Sam Wilson',
            heroName: 'Falcon'
        }
    }
}];

const output = [];

// Now destructure it
for (const {
    character: {
        heroName,
        partner: {
            heroName: partnerHeroName
        }
    }
} of avengers) {
    // heroName and partnerHeroName are destructured
    output.push(heroName + " & " + partnerHeroName);
}

// Output:
output;

Note that this will only work if each object being destructured has the same structure. If one of your objects does not have the property being destructured, your code will break. For example:

// Warning : does NOT work!
const avengers = [{
	character: {
    	heroName: 'Captain America',
        realName: 'Steve Rogers',
        partner: {
        	realName: 'Sam Wilson',
            heroName: 'Falcon'
        }
    }
}, {
	character: {
    	heroName: 'Black Widow',
        realName: 'Natasha Romanoff'
    }
}];

const output = [];
for (const {
	character: {
		heroName,
       partner: {
       	heroName: partnerHeroName
       }
    }
} of avengers) {
	// Throws an error when Black Widow is reached, 
        // as she neither has nor needs a partner.
	output.push(heroName + " & " + partnerHeroName);
}

// Output:
output;

This will break on Black Widow, as there is no partner object. So if you do decide to use destructuring to parse your data feeds, always wrap the destructuring expression in a try...catch block, and handle any unexpected or missing property accordingly.

Fortunately, we can easily fix this with a default value for partner :

// This DOES work, by using a default value for missing partner
const avengers = [{
    character: {
        heroName: 'Captain America',
        realName: 'Steve Rogers',
        partner: {
            realName: 'Sam Wilson',
            heroName: 'Falcon'
        }
    }
}, {
    character: {
        heroName: 'Black Widow',
        realName: 'Natasha Romanoff'
    }
}];

const output = [];
for (const {
        character: {
            heroName,
            partner: {
                heroName: partnerHeroName
            } = { heroName: 'none' } // default value for heroName, 
                                     // sets partnerHeroName to 'none'
        }
    }
    of avengers) {
    output.push(heroName + " & " + partnerHeroName);
}

// Output (no error now):
output;

Parse an HTTP request in ExpressJS for Node

The ExpressJS middleware for Node parses the parameters in an HTTP request and assembles them as object properties assigned to its request object. This object then becomes a hierarchical data structure representing all the parameters the client sent in the HTTP request.

For example, ExpressJS will take an HTTP request with a queryString in the URL and assign the queryString’s parameters to the request object’s query property.

We can access those properties in ES5 like so:

// ES5 way of accessing the queryString parameters:

// Suppose our URL is:
// /search?character[heroName]=ironMan&character[partner]=warMachine

// Then our ExpressJS req object would look like this:
var req = { query: { character: { heroName: 'ironMan', partner: 'warMachine' } } };

// To access the req object's properties, we'd need to do this:
var heroName = req.query.character.heroName
// ironMan

var partner = req.query.character.partner
// warMachine

// Output:
eval('"heroName: " + heroName + ", partner: " + partner');

Not exactly difficult, but if you have a lot of parameters to access, this method becomes tedious very quickly. But with ES6 destructuring, we can do it all in a single line:

// The ES6 way with destructuring:

// Suppose our URL is:
// /search?character[heroName]=ironMan&character[partner]=warMachine

// Our ExpressJS req object would look like this:
var req = { query: { character: { heroName: 'ironMan', partner: 'warMachine' } } };

// And we can now access req's properties using destructuring:
const {
    query: {
        character: {
            heroName,
            partner
        }
    }
} = req;
// heroName = ironMan
// partner = warMachine

eval('"heroName: " + heroName + ", partner: " + partner');

Summary

That’s the end of this gigantic deep-dive into destructuring. Once you understand how you can use destructuring, it’ll quickly become one of the most-used techniques in your code, to the extent that code that doesn’t use it starts to look, frankly, quaint!

I hope I’ve given you enough examples to both understand what destructuring is, how you use it in different general circumstances, and how you can use it for specific solutions.

If you’ve got any more examples you use that you want to share, feel free to mention them in the comments below (just wrap <code></code> tags around your code).

All posts in this series on destructuring





About List