|||

JavaScript (ES2015+) Enlightenment

Grokking Modern JavaScript, In The Wild

Written by Cody Lindley



Sponsored by Frontend Masters, advancing your skills with in-depth, modern front-end engineering courses

Today, tools like Babel have made it commonplace to see ES2015 , ES2016, ES2017, ES2018, and ES2019 language updates/proposals in babelified source code. These compounding language changes can make it difficult to learn something like React, Apollo GraphQL, or Webpack.

This book aims to alleviate this problem by providing a curated selection of the commonly used language updates, tersely explained, to lessen this indirection. Thus, after studying the material in this book grokking new JavaScript code while learning JavaScript frameworks and tools, should be much more comfortable.

Written For:

The contents of this book are for developers who are working in a codebase using modern React, Vue, or Angular code and find recent JavaScript language updates/proposals to be causing too much indirection. And or, developers who want to drill into memory the latest and most commonly used JavaScript updates.

ES2015+ Enlightenment is not a rudimentary read on the JavaScript language. The content in this book attempts to take a developer with ES3 and ES5 knowledge and make them more knowledgeable about ES2015+ and the implications of modern changes to the language on JavaScript tools and frameworks.

How to Use/Read This Book:

First off, this is a mix between a book, a reference, and cheatsheet. My intention in writing is to shine a light on ES5+ language changes in a tersely and helpfully format. To be clear this is not a long form book on the JavaScript language. Or, a detailed reference. Consider this an elaborate cheatsheet with runnable code purposefully curated for those who know ES3 but need to master ES5+.

Second, this is a web book. A lot of contexts can be gained by just clicking on links in this book. If you ever feel in need of more context use the links in the text.

How to Use/Read The Code Examples:

Try and view the code examples as an extension of the words. First, read and re-read the words. Then read the code, especially the code comments, from top to bottom as if they are part of the surrounding paragraphs. The goal should be to grok the code until no questions remain as to what the code example is doing and expressing.

While Using/Reading the book remember, by design:

The words and code comments are intentionally terse with the goal of code comprehension without long-winded and exhaustive explanations. The code examples are contrived to reveal the nature of the code. Focus on what the code is doing and making sure you understand it, potentially over my words. The book is a mix of a mini book, a reference, and cheatsheet. Expect it to feel like one of these or all of these at the same time.

Contribute content, suggestions, and fixes on github:

https://github.com/FrontendMasters/javascript-enlightenment

Chapter 1 : ECMAScript 5 (aka ES5) Recap In this chapter, I'll recap the significant language updates introduce in ES5 to delineate these updates from the updates made in ES2015 (aka ES6). 1.1 : ES5 Browser and Node Compatibility For the most part, ES5 is compatible with modern browsers (e.g. IE9+, excluding strict mode) and Node since version 4.x.x. Unless you have to support an older JavaScript engine/runtime (e.g. IE8) you are safe to assume most modern JavaScript engines/runtimes support ES5. 1.2 : New ES5 String Method The ES5 .trim() method removes whitespace from both ends of a string and creates a new string. var myString = ' Some Tabs and Spaces '; console.log(myString.length); // logs 28 var myNewString = myString.trim(); // trim it console.log(myNewString); // logs 'Some Tabs and Spaces' console.log(myNewString.length); // logs 20 // Note: this method does not mutate a value it creates a new value console.log(myString, myString.length); // This still is, ' Some Tabs and Spaces ' You should consider "Whitespace" to mean in general; spaces, tabs, and non-breaking spaces used in a string. Specifically trim() removes: \U0009 character tabulation

\U000A line feed

\U000B line tab

\U000C form feed

\U000D carriage return

\U0020 space

\U3000 ideographic space

\UFEFF zero-width non-breaking space 1.3 : New ES5 Array Static Methods ES5 added the static Array method, Array.isArray() . The Array.isArray() method is used to determine precisely ( true or false ) if a value is a true Array . In other words, this method checks to see if the provided value is an instance of the Array() constructor. console.log(Array.isArray([1,2,3])) //logs true // Note: does not work on Array-like objects console.log(Array.isArray({length: 3, 0:1, 1:2, 2:3})) //logs false Notes: The static Array.isArray() method differs from using [] instanceof Array only slightly when dealing with iframes. This isArray() method also respects values that are constructed from constructors extended from the native Array constructor using the new class extends keyword. 1.4 : New ES5 Array Methods ES5 added the following Array methods (i.e. higher-order iteration functions): [].some()

[].every()

[].filter()

[].forEach()

[].indexOf()

[].lastIndexOf()

[].map()

[].reduce()

[].reduceRight() The [].some() method will start testing values in array, until a test returns true, then the function passed to .some() immediately returns true , otherwise the function returns false (i.e. the first truthy value found will result in the function immediately returning true and potentially this could mean not all tests are run). // Check if one or more items in the array is bigger than or equal to 2 var someMethod = [1, 2, 3].some(function(value, valueIndex, wholeArray){ return value >= 2; }); console.log(someMethod) // logs true because the array contains a value that is greater than or equal to 2 The [].every() method will start testing values in array, until a test returns false, then the function passed to .every() immediately returns false , otherwise the function returns true (i.e. the first falsy value found will result in the function immediately returning false and potentially this could mean not all tests are run). // Check if every item in the array is bigger than or equal to 2 var everyMethod = [1, 2, 3].every(function(value, valueIndex, wholeArray){ return value >= 2; }); console.log(everyMethod) // logs false because the array contains a value that is less than 2 The [].filter() method will return a new Array containing all the values that pass (i.e. are true) the filtering test. var myArray = [1,2,3]; // filter out any value in the array that is not bigger than or equal to 2 var FilteredArray = myArray.filter(function(value, valueIndex, wholeArray){ return value >= 2; }); console.log(FilteredArray) // logs [2,3] // Note: filter() returns a new Array, myArray is still equal to [1,2,3] The [].forEach() method executes a provided function for each value in the array. // log to the console each value, valueIndex, and wholeArray passed to the function ['dog','cat','mouse'].forEach(function(value, valueIndex, wholeArray){ console.log('value = '+value+' valueIndex = '+valueIndex+' wholeArray = '+wholeArray); /** logs: "value=dog valueIndex=0 wholeArray=dog,cat,mouse " "value=cat valueIndex=1 wholeArray=dog,cat,mouse " "value=mouse valueIndex=2 wholeArray=dog,cat,mouse " **/ }); The [].indexOf() method searches an array for the first value matching the value passed to indexOf() , and returns the index of this value. // get index of first 'cat' console.log(['dog','cat','mouse', 'cat'].indexOf('cat')); // logs 1 // Note: Remember the index starts at 0 The [].lastIndexOf() method searches an array for the last value matching the value passed to [].lastIndexOf() , and returns the index of this value. // get index of last 'cat' console.log(['dog','cat','mouse', 'cat'].lastIndexOf('cat')); // logs 3 // Note: Remember the index starts at 0 The [].map() method executes a provided function for each value in the array, and returns the results in a new array. var myArray = [5, 15, 25]; // add 10 to every number in the array var mappedArray = myArray.map(function(value, valueIndex, wholeArray){ return value + 10; }); console.log(mappedArray) // logs [15,25,35] // Note: map() returns a new Array, myArray is still equal to [5, 15, 25] The [].reduce() method runs a function that passes the return value to the next iteration of the function using values in the array from left to right and returning a final value. // add up numbers in array from left to right i.e. (((5+5) +5 ) + 2) var reduceMethod = [5, 5, 5, 2].reduce(function(accumulator, value, valueIndex, wholeArray){ return accumulator + value; }); console.log(reduceMethod) // logs 17 /** reduce also accepts a second parameter that sets the first accumulator value, instead of using the first value in the array. **/ // add up numbers in array from left to right, but start at 10 i.e. ((((10+5) +5 ) +5 ) + 2) var reduceMethod = [5, 5, 5, 2].reduce(function(accumulator, value, valueIndex, wholeArray){ return accumulator + value; // first iteration of func accumulator is 10 not 5 }, 10); console.log(reduceMethod) // logs 27 The [].reduceRight() method runs a function that passes the return value to the next iteration of the function using values in the array from right to left and returning a final value. // add up numbers in array from left to right i.e. (((2+5) +5 ) + 5) var reduceRightMethod = [5, 5, 5, 2].reduceRight(function(accumulator, value, valueIndex, wholeArray){ return accumulator + value; }); console.log(reduceRightMethod) // logs 17 /** reduce also accepts a second parameter that sets the first accumulator value, instead of using the first value in the array. **/ // add up numbers in array from left to right, but start at 10 i.e. ((((10+2) + 5 ) +5 ) + 5) var reduceRightMethod = [5, 5, 5, 2].reduceRight(function(accumulator, value, valueIndex, wholeArray){ return accumulator + value; // first iteration of func accumulator is 10 not 5 }, 10); console.log(reduceRightMethod) // logs 27 Notes: All the new methods ignore holes in arrays (i.e. [1,2,,,,,,,,,3] ). All of these new Array methods, except for reduce and reduceRight accept a second parameter. This second parameter allows you to set the this value for the function being passed in the first parameter. 1.5 : New ES5 Getters and Setters (aka Accessors Descriptors or Computed Properties) ES5 adds to Objects computed properties via the keywords get and set . This means that Objects can have properties, that are methods, but don't act like methods (i.e you don't invoke them using () ). In short by labeling a function in an object with get or set one can invoke the backing property function on a property, by merely accessing the property, without using innovating brackets. The example below demonstrates the nature of getter and setter properties: var obj = { get RunsWhenAccessed(){ console.log('you accessed the property RunsWhenAccessed'); }, set RunsWhenSet(newValueBeingSet){ console.log('you set the property RunsWhenSet to : ' + newValueBeingSet); } } // access the RunsWhenAccessed property and the backing property function fires obj.RunsWhenAccessed; // logs 'you accessed the property RunsWhenAccessed' // access and set the RunsWhenSet property and the backing property function fires obj.RunsWhenSet = 'foo'; // logs 'you set the property RunsWhenSet to : foo' // note I am setting a value that becomes an argument and not calling a function using brackets Don't over think getters and setters, they are simply a property who's value is determined by running a backing property function and the function is invoked by accessing or setting the property. var person = { firstName : '', lastName : '', get name() { return this.firstName + ' ' + this.lastName; }, set name(str) { var n = str.split(/\s+/); this.firstName = n.shift(); this.lastName = n.join(' '); } } // set name, but store first and last separately person.name = 'Cody Lindley'; // get name, returns firstName and LastName combined console.log(person.name); // logs 'Cody Lindley' Notes: The same property can have a getter and setter. The get and set syntax is a shortcut for using Object.defineProperty() and Object.defineProperties() to add the get and set property descriptors. A setter property can only take in a single value and thus a single argument is passed to the backing property function. 1.6 : New ES5 Object Static Methods Object.create()

Object.getPrototypeOf()

Object.defineProperty()

Object.defineProperties()

Object.getOwnPropertyDescriptor()

Object.getOwnPropertyNames()

Object.preventExtensions()

Object.isExtensible()

Object.sealed()

Object.isSealed()

Object.freeze()

Object.isFrozen() ES5 added the Object.create() method so objects could be created and their prototypes easily setup. Object.getPrototypeOf () was added to easily get an objects prototype. // setup an object to be used as the prototype to a newly created myObject object below var aPrototype = { foo: 'bar', getFoo: function(){ console.log(this.foo); } } // create a new myObject, setting the prototype of this new object to aPrototype var myObject = Object.create(aPrototype); // logs 'bar' because myObject uses aPrototype as its prototype, it inherits getFoo() myObject.getFoo(); // logs 'bar' // get a reference to the prototype of myObject, using getPrototypeOf() console.log(Object.getPrototypeOf(myObject) === aPrototype); //logs true ES5 added Object.defineProperty() , Object.defineProperties() , and Object.getOwnPropertyDescriptor() so object properties can be precisely defined (using descriptors) and retrieved. Descriptors provide an attribute that describe a property in an object. The attributes (i.e descriptors) that each property can have are: configurable, enumerable, value, writable, get, and set. // create an object with a property and value const myObject = { prop1: 'value1' } // get the default descriptors for the prop1 property in myObject. console.log(Object.getOwnPropertyDescriptor(myObject,'prop1')); /** the above console logs: [object Object] { configurable: true, enumerable: true, value: "value1", writable: true } Note that get and set are undefined by default **/ // add a property, 'value2' with descriptors to myObject using Object.defineProperty() Object.defineProperty(myObject, 'prop2', { value: 'value2', writable: true, enumerable: true, configurable: true, }); // get the descriptors for the prop2 property. console.log(Object.getOwnPropertyDescriptor(myObject,'prop2')); // add multiple properties ('prop3' & 'prop4') with // descriptors to myObject using Object.defineProperties() Object.defineProperties(myObject, { prop3: { enumerable: true, configurable: true, value: 'value3' }, prop4: { enumerable: true, configurable: true, // Note that value and write properties are not added when using the properties set and get set: (newValue) => { console.log('you set the property prop4 to : ' + newValue); }, get: () => { console.log('you accessed the property prop4'); return 'prop4'; // the get returns the value for prop4 unlike using, value: 'value4' } } }); // get the descriptors for the prop3 and prop4 properties console.log(Object.getOwnPropertyDescriptor(myObject,'prop3')); console.log(Object.getOwnPropertyDescriptor(myObject,'prop4')); // Note that prop4's value is based on get and set, not value console.log(myObject.prop4); Notes: Using = to assign an object a property and value is a similar routine but not exactly identical to using Object.defineProperty() and Object.defineProperties() . These two methods allow the assignment of a value as well as the defining/retrieval of a properties descriptors and will ignore the prototype chain (i.e. will not look for inherited properties). ES2017 added the Object.getOwnPropertyDescriptors() static method. This method returns an object containing all the own property descriptors for a given object. Below are the descriptions/definitions of the attributes of a property that make up a property descriptor:



value : contains the property's value. writable : contains a boolean indicating whether the value of a property can be changed or written too. get : reference to the function that is called when a property is read. set : reference to the function that is called when a property is set to a value. configurable : contains a boolean indicating whether a property can have its attributes changed and deleted. enumerable : contains a boolean indicating if a property will show up on certain operations. ES5 added Object.keys() which returns an Array of non-inherited-enumerable properties of a given object. ES5 added Object.getOwnPropertyNames() which returns an Array of non-inherited properties of a given object regardless of enumerability. // Create an object var myObject = Object.create(null); // no prototype used // Add prop to object created myObject.myObjectProp1 = 1; // Define a property using defineProperty() Object.defineProperty(myObject, 'myObjectProp2', { enumerable: false, value: 2 }); // Use keys() to get Array of all non-inherited, enumerable properties console.log(Object.keys(myObject)); // logs ["myObjectProp1"] // Use getOwnPropertyNames to get Array of all non-inherited properties including non-enumerable console.log(Object.getOwnPropertyNames(myObject)); // logs ["myObjectProp1", "myObjectProp2"] Notes: ES2017 added Object.values() which returns an array of a given object's own enumerable property values and Object.entries() which returns an array of a given object's own enumerable properties and values (e.g. [[property:value],[property:value]] ). ES5 provided three Object methods for protecting objects. They are: Object.preventExtensions() : Stops properties from being added but not deleted. Object.seal() : Stops properties from being added or configured (i.e. the configurable descriptor attribute for each property is changed to false ). Object.freeze() : Stops properties from being added, configured, or writable (i.e. the configurable and writable descriptor attribute for each property is changed to false ) To compliment these three methods ES5 also added three Object methods for determining the type of protection an object is using. They are: Object.isExtensible() : Boolean check if an object is extensible. Object.isSealed() : Boolean checking if an object is sealed. Object.isFrozen() : Boolean checking if an object is frozen. 1.7 : New ES5 bind() Function Method Before ES5 functions could only be invoked and given a this value at innovation time using apply() or call() . In other words, these two methods make it possible to call a function and at call time change the value of this for the body of the function. But what if you don't want to invoke the function? And instead, you want to change the value of this for the function when it is called in the future? ES5 added bind() and this new function method does not invoke a function but instead takes an existing function and from it creates a new function, yet to be called, with a specified value for this inside of the new function. window.name = 'John'; // Defined in the global scope var myObject = {name:'Bill'}; var greeting = function(){ // if the greeting function has a defined this that is not window i.e. global scope console.log(this !== undefined && this !== window ? this.name : window.name); }; // invoke greeting, where the this context for the greeting function is the global scope greeting(); //logs John because the value name is in the global scope // .bind() greetings function this value to myObject var bindGreetingToObject = greeting.bind(myObject); // this keyword now points to myObject, and not the window object. // invoke bindGreetingToObject with the this context being bound to myObject bindGreetingToObject(); //logs Bill because this value for bindGreetingToObject is bound to myObject Notes: Don't forget that the main difference between apply() and call() is that apply() takes an Array of arguments passed to the called function while call() takes a list of individual arguments (e.g. arg2, arg3, ... ). Bind() also takes a list of individual arguments (e.g. arg2, arg3, ... ) passed to the new function being called. 1.8 : New ES5 use strict Mode Adding, 'use strict' to the top of a JavaScript file or as the first line of a function body will change the language to a stricter version of JavaScript. Today, using strict mode isn't typically a decision to be made because ECMAScript modules are implicitly in strict mode. In other words, spinning up a version of say, create-react-app which uses ECMAScript modules will have 'use strict' in play by default due to the fact that ECMAScript modules (i.e. import React from 'react'; ) uses strict mode implicitly. It is important you are aware of this fact. In other words, JavaScript modules give you strict mode by default. 1.9 : New ES5 JSON methods JSON.parse() takes a JSON string and returns the JavaScript value(s) described by the string. In other words, JSON.parse() will convert a string of JavaScript in JSON format into real JavaScript values (i.e. Objects, Arrays, Strings, Numbers, Booleans etc...). var JSONValues = JSON.parse('{"name":"Bill","age":22}') // convert JSON string to JS values console.log(typeof JSONValues); // logs "object" console.log(JSONValues.name, JSONValues.age); // logs Bill, 22 JSON.stringify() takes JavaScript values and returns a string representing the values. var JSONString = JSON.stringify({ name: 'Bill', age: 2 }); // Convert JS Object to JSON String console.log(typeof JSONString) // logs "string" console.log(JSONString) // logs "{ "name": "Bill", "age":2} " Notes: Both stringify() and parse() have an optional second function parameter that can be used to augment the result before it is returned. 1.10 : New ES5 Syntax Changes Trailing commas in Object literals are now ok: var myObject = { name: 'Bill', age: 12, // no syntax error } Notes: Be aware, trailing commas are not allowed in JSON. ES2017 will allow trailing commas when defining function parameters or calling a function with arguments. However, calling a function with a comma alone or defining a function parameter as a comma alone will throw a SyntaxError. Reserved words can now be used as unquoted Object property keys: // no syntax error when using reserved keywords as property/keys on an object var myObject = { new: 'new', class: 'class', if: 'if', function: 'function' } 1.11 : From ES5 to ES2015. What? ES2015 was first called ES6 because at the time an update to the 5th edition of ECMAScript would logically be title ECMAScript edition 6 or "ES6". However, a naming tweak for language updates/changes occurred in 2015. It was decided by TC39, the standardization group for JavaScript/ECMA-262, to release stage four proposals once a year (i.e. stage four are approved changes to the language). Given this change, new updates to the language moving forward would be given the titles ES2015 (i.e. ES6), ES2016, ES2017, ES2018 etc... . Basically, language changes/updates are semantically titled under the year in which the update/change becomes standardized. Notes: If it is not obvious, it should be noted that just because a JavaScript language update/change has been standardized does not mean those who make use of the ECMAScript standard will implement the updates/change (i.e., adoption of new standards is a slow and often complicated affair e.g., browser compatibility).

Chapter 3 : New ES2015+ Methods In this chapter, I'll break down the newest methods from ES2015+. When targeting an ES5 only runtime (i.e. IE9) all of these methods have to be polyfilled. 3.1 : New Number Static Method (ES2015) ES2015 added the Number.isInteger() method that will return true if the value passed to it is an integer (i.e. a number with no fractional part). Otherwise, it will return false . console.log(Number.isInteger(0)); // logs true console.log(Number.isInteger(1)); // logs true console.log(Number.isInteger(0.1)); // logs false, has a fractional part console.log(Number.isInteger(Infinity)); // logs false console.log(Number.isInteger([1])); // logs false 3.2 : New String Methods (ES2015, ES2017) ''.startsWith() ES2015

ES2015 ''.endsWith() ES2015

ES2015 ''.includes() ES2015

ES2015 ''.repeat() ES2015

ES2015 ''.padStart() ES2017

ES2017 ''.padEnd() ES2017

ES2017 ''.matchAll() (coming soon, stage 3 proposal)

(coming soon, stage 3 proposal) ''.trimStart() / ''.trimLeft() (coming soon, stage 3 proposal)

/ (coming soon, stage 3 proposal) ''.trimEnd() / ''.trimRight() (coming soon, stage 3 proposal) ES2015 added ''.startsWith() and ''.endsWith() . These methods can check if a string begins or ends with a specific sub-string. // True or false does 'pre-funded' start with 'pre-' console.log('pre-funded'.startsWith('pre-')) // logs true // True or false does 'pre-funded' end with 'funded' console.log('pre-funded'.endsWith('funded')) // logs true ES2015 added ''.includes() . This method will check if a string contains a specific sub-string. // True or false does 'pre-funded' include the sub string 'fund' console.log('pre-funded'.includes('fund')) // logs true ES2015 added ''.repeat() . This method will take a string and return the same string repeated as many times as provided in the first argument. // Take the string 'He' and return a string containing 'He' repeated three times console.log('He'.repeat(3)) // logs 'HeHeHe' ES2017 added ''.padStart() and ''.padEnd() . These methods will pad the beginning or end of a string with repeating non-breaking spaces (the default) or a specified string of characters. When padding you supply the final length of the entire string. Including the original string. The non-breaking spaces or specified string will fill in any of the characters not taken up by the original string. // Pad the start of 'GO!' with spaces so the total length of the string is 10, including 'GO!'. console.log('GO!'.padStart(10)) // logs ' GO!' // note how the ' ' repeated until a length of 10 was reached // Pad the start of 'GO!' with '.' so the total length of the string is 10, including 'GO!'. console.log('GO!'.padStart(10, '.')) // logs '.......GO!' // note how the '.' repeated until a length of 10 was reached // Pad the end of 'GO!' with spaces so the total length of the string is 10, including 'GO!'. console.log('GO!'.padEnd(10)) // logs 'GO! ' // note how the ' ' repeated until a length of 10 was reached // Pad the start of 'GO!' with '.' so the total length of the string is 10, including 'GO!'. console.log('GO!'.padEnd(10, '!')) // logs 'GO!!!!!!!!' // note how the '!' repeated until a length of 10 was reached Notes: The string methods ''.matchAll() , ''.trimStart(); ''.trimLeft(); , and ''.trimEnd(); ''.trimRight(); are currently at stage 3. 3.3 : New Array Static Methods (ES2015) Array.from()

Array.of() ES2015 added the Array.from() static method. This method will take Array-like values (objects with a length property and indexed values) or iterable values and convert them into Array values (iterable values are: String, Array, TypedArray, Map, and Set). // Array from Array-like values const myArray1 = Array.from({length: 2, 0: 'zero', 1:'one'}); console.log(myArray1); // logs ["zero", "one"] // Array from String (i.e. an iterable) const myArray2 = Array.from('foo'); console.log(myArray2); // logs ["f", "o", "o"] // Array from an Array (i.e. an iterable) const myArray3 = Array.from([1, 2, 3]) console.log(myArray3); // logs [1, 2, 3], well that is silly // Array from an Array (i.e. an iterable), where each item in the array is run through a function // The second argument to .from() can be a function called on each iterable const myArray4 = Array.from([1, 2, 3], item => item * item) console.log(myArray4); // logs [1, 4, 9] Notes: Array.from() creates a new, shallow-copied Array. ES2015 added the Array.of() static method. This method creates an Array from arguments. Unlike the array constructor it can handle a case like Array.of(5) resulting in [5] while Array(5) will result in [undefined, undefined, undefined, undefined, undefined] . // create an array containing 5 values console.log(Array.of(5,{},undefined,[],'string')); /* logs: [5, [object Object] { ... }, undefined, [], "string"] */ // create an array containing 5 undefined values console.log(Array(5)); // works if only one argument is passed /* logs: [undefined, undefined, undefined, undefined, undefined] */ // create an array containing 2 numeric values, 5 and 4 console.log(Array(5,4)); /* logs: [5, 4] */ 3.4 : New Array Methods (ES2015, ES2016) [].findIndex()

[].find()

[].includes()

[].keys()

[].values()

[].entries()

[].copyWithin()

[].fill()

[].flat() (coming soon, stage 3 proposal)

(coming soon, stage 3 proposal) [].flatMap() (coming soon, stage 3 proposal) ES2015/ES2016 added the [].findIndex() , [].find() and [].includes() methods. The [].find() method is used to find a specific value in an array and return that value. [].findIndex() is used to find a specific value and return its index in the array. Both use a testing function to iterate over the Array and return the first truthy value returned from the testing function. The [].includes() method is used to verify (true or false) if an array contains a specific value. const myArray = [10, 20, 30, 40]; // find and return the first value in the array that is greater than 20 console.log(myArray.find(function(item){ return item > 20})) // logs 30 // find and return the index of the first value in the array that is greater than 20 console.log(myArray.findIndex(function(item){ return item > 20})) // logs 2 // does myArray contain the value 30 console.log(myArray.includes(30)) // logs true ES2015 added the [].keys() , [].values() , and [].entries() methods. The [].keys() method returns the keys from an Array as an Array iterator object. The [].values() method returns the values (i.e. the items) from an Array as an Array iterator object. And the [].entries() returns an Array iterator containing each item as a key-value array (i.e. [[0, item0], [1, item1]] ). let myArray = ['item0', 'item1', 'item2']; // All of these log "[object Array Iterator] " console.log(myArray.keys().toString()); console.log(myArray.values().toString()); console.log(myArray.entries().toString()); // create an iterator containing the index's of an array (i.e. the key's) let myArrayKeys = myArray.keys(); console.log(myArrayKeys.next().value) // logs 0 console.log(myArrayKeys.next().value) // logs 1 console.log(myArrayKeys.next().value) // logs 2 // create an iterator containing the values from an array let myArrayValues = myArray.values(); console.log(myArrayValues.next().value) // logs "item0 " console.log(myArrayValues.next().value) // logs "item1 " console.log(myArrayValues.next().value) // logs "item2 " // create an iterator containing both the keys and the values, inside of an array let myArrayEntries = myArray.entries(); console.log(myArrayEntries.next().value) // logs [0, "item0"] console.log(myArrayEntries.next().value) // logs [1, "item1"] console.log(myArrayEntries.next().value) // logs [2, "item2"] Notes: The [].keys() , [].values() , and [].entries() array methods are very similar to the [].keys() , [].values() , and [].entries() found on the Map() and Set() values. An iterator is basically an object with a .next() method. Don't confuse an iterator with an iterable. An iterator will keep track of what comes next (i.e. .next() ) while a iterable value is simply a value that comes with an interface for iterating over the value. An array is by default an iterable. But, methods like [].keys() , [].values() , and [].entries() can be used to create an Array iterator object around the original array that will keep track of the current iteration and knows what the next item in the iteration is and how to get it. ES2015 added the [].copyWithin() method. This method shallow copies a range of items in an Array and then inserts the copies back into the same Array replacing items in the Array starting at a specific index. let myArray1 = [1,1,1,1,5,5,5,5]; // copy from index 4 to 6 and take the copies and start replacing at index 0 console.log(myArray1.copyWithin(0, 4, 6)); //logs [5, 5, 1, 1, 5, 5, 5, 5] // i.e. this will replace the values at index 0 and index 1 with copied values 5, 5 let myArray2 = [1,1,1,1,5,5,5,5]; // passing negative numbers to any of the parameters means start from the end of the array // copy from -4 index to -2 and take the copies and start replacing at index 2 console.log(myArray2.copyWithin(-6, -4, -2)); //logs [1, 1, 5, 5, 5, 5, 5, 5] // i.e. this will replace the values at index 2 and index 3 with copied values 5, 5 let myArray3 = [1,1,1,1,5,5,5,5]; // copy from index 4 to end and take the copies and start replacing at index 0 console.log(myArray3.copyWithin(0, 4)); //logs [5, 5, 5, 5, 5, 5, 5, 5] // i.e. this will replace the values at index 0, 1, 2, 3 with copied values 5, 5, 5, 5 Notes: The second and third arguments passed to [].copyWithin() will accept negative numbers indicating a range which starts or counts from the end not the beginning. ES2015 added the [].fill() method. This method will replace or fill a range of items in an Array with a new value. // replace the values at index 0 and index 1 with 'foo' // the second and third arguments to fill() below say, start filling at index 0 and stop at index 2 console.log([5,5,5,5].fill('foo',0,2)) // logs ["foo", "foo", 5, 5] // replace all values from index 2 on with 'foo' console.log([5,5,5,5].fill('foo',2)) // logs [5, 5, "foo", "foo"] // replace all values with 'foo' console.log(Array(4).fill('foo')) // logs ["foo", "foo", "foo", "foo"] Notes: The second and third arguments passed to [].fill() will accept negative numbers indicating a range which starts or counts from the end not the beginning. 3.5 : New Object Static Methods (ES2015, ES2017) Object.is()

Object.values()

Object.entries()

Object.assign()

Object.fromEntries() (coming soon, stage 3 proposal) ES2015 added the Object.is() static method. This method accepts two values and if the values are the same then it returns true, otherwise, it returns false. // compare the same Object object const myObject = {}; console.log(Object.is(myObject,myObject)); // logs true, same // compare different Object objects console.log(Object.is({},{})); // logs false, two different objects // compare primitive values const myNumber = 5; const myBoolean = true; const myString = ''; const myNull = null; const myUndefined = undefined; console.log(Object.is(5,myNumber)); // logs true, same value console.log(Object.is(true,myBoolean)); // logs true, same value console.log(Object.is('',myString)); // logs true, same value console.log(Object.is(null,myNull)); // logs true, same value console.log(Object.is(undefined,myUndefined)); // logs true, same value Notes: Object.is(1,1) is the same as 1 === 1 , except for the following cases, Object.is( +0, -0 ) is false, while -0 === +0 is true and Object.is( NaN, NaN ) is true, while NaN === NaN is false. ES2017 added the Object.values() static method. This method will return an Array of enumerable property values from an Object . // log the values in myObject var myObject = { 0: 'f', 1: 'o', 2: 'o' }; console.log(Object.values(myObject)); // logs ['f', 'o', 'o'] // the string primitive value will be coerced to an object if passed to Object.values(); console.log(Object.values('foo')); // logs ['f', 'o', 'o'] ES2017 added the Object.entries() static method. This method will return an objects properties, as key-value pairs inside an Array (e.g. [property, value] ) inside a single wrapping Array (e.g. a multidimensional array [[property, value], [property, value]] ). // log the key and value pairs in myObject var myObject = { 0: 'f', 1: 'o', 2: 'o' }; console.log(Object.entries(myObject)); // logs [["0", "f"], ["1", "o"], ["2", "o"]] Notes: Don't forget Object.keys() was added in ES5. ES2015 added the Object.assign() static method. This method copies own enumerable properties from one object to a different target object. // using assign() to clone an object const myObject = {'key1':'value1', 'key2':'value2'}; console.log(Object.assign({}, myObject)); /* logs new object, cloned from myObject [object Object] { key1: "value1", key2: "value2" } */ // using assign() to merge objects const myObject1 = {'key1':'value1', 'key2':'value2'}; const myObject2 = {'key3':'value3', 'key4':'value4'}; const myObject3 = {'key4':'4'}; console.log(Object.assign(myObject1, myObject2, myObject3)); /* logs a new object [object Object] { key1: "value1", key2: "value2", key3: "value3", key4: "4" } Note: same properties are overwritten. Last in to the parameter list wins. */ // using assign to coerce a string to an object console.log(Object.assign({},'foo')); /* logs a new object [object Object] { 0: "f", 1: "o", 2: "o" } */ Notes: The Object.assign() won't deep clone reference values; it will only deep clone the reference, not the value. This means that if you are cloning an object with reference values (e.g. objects inside of objects) the reference value is not cloned just the reference. Thus, deep cloning using Object.assign() copies pointers (i.e. the reference), not values. If you need to deep clone an object, you'll have to resort to other methods. The Object.assign() is commonly used to merge objects together or create shallow clones of objects.

Chapter 4 : New ES2015+ Syntax In this chapter, I'll break down the most used syntax updates from ES2015 and beyond. When targeting an ES5 only runtime these syntax updates have to be compiled from ES2015+ to ES5 (e.g., Babel > ES5). 4.1 : Using const and let (ES2015) Before ES2015 variables were declared using the var keyword. Today, it is more common that developers completely avoid the use of var and instead use const and let when needed. Const is used to declare variables with values that do not get reassigned. Reassigning a const value throws an error. Additionally, both const and let honor all block scope (i.e. { block scope } ), unlike var which only honors function block scope (i.e. function myFunction(){ block scoped } ). This means const and let are safer because they don't leak out of things like if blocks or looping blocks. For example, in the code below the variables, doug and MATH are scoped to the if block while jill leaks out. // MATH_CONSTANT and doug are scoped within the if(){ ... } blocks and unlike var they will not leak out. if(true){ let doug = 45; const MATH_CONSTANT = Math.PI; var jill = 44; // does not care about brackets } console.log(jill); // logs 44, because it leaked out of { } // try { console.log(doug); console.log(MATH_CONSTANT); }catch(e){ console.log('Can\'t find doug or MATH_CONSTANT'); } Notes: Keep in mind, using const does not create an immutable value, it just means that the variable can't be reassigned. Thus, changing the properties in something like an Object or the values in an Array that has been assigned to a const will not throw an error because it is not being reassigned (i.e. the reference/pointer to the value did not change thus no error). Today expect to see code that never uses var , favors const , and uses let only when re-assignment is needed (i.e. let score = 0; score = 1; v.s. const pie = 3.14; ). 4.2 : Using blocks to Create Scope (ES2015) Before ES2015 if one needed a unique scope the only option was to use function scope: // below doug and MATH_CONSTANT are only available with the scope of the function (function () { var doug = 45; var MATH_CONSTANT = Math.PI }()); try { console.log(doug); console.log(MATH_CONSTANT); }catch(e){ console.log('Can\'t find doug or MATH_CONSTANT'); } Today, given that const and let remain scoped to blocks with no leaking developers can replace IIFE with blocks if var is avoided. So, don't be surprised if you see IIFE's replaced with simple blocks: // doug and MATH_CONSTANT only available with the scope of the blocks { let doug = 45; const MATH_CONSTANT = Math.PI; } try { console.log(doug); console.log(MATH_CONSTANT); }catch(e){ console.log('Can\'t find doug or MATH_CONSTANT'); } Notes: Keep in mind that the need to construct a private scope is being accomplished today with ES2015 modules. Modules have their own module scope by default. In other words, in modern code, ECMAScript modules are already scoped privately simply by creating the file. One does not need to create another private scope on top of that. 4.3 : Using Default Function Parameters (ES2015) In the past, if a default value was needed for an argument passed to a function, boilerplate was needed: const add = function(x, y) { x = x || 0; y = y || 0; return x + y; } console.log(add()) //logs 0 No longer is this the case. It is now possible to give parameters default values when defining a function. The code below is equivalent to the previous code. const add = function(x = 0, y = 0) { return x + y; } console.log(add()) //logs 0 Notes: Parameter default values are triggered by undefined , not simply a falsely value like null or '' . The arguments array, available within the scope of a function, is not affected by default parameters values in any way. 4.4 : Using Destructuring Assignments (ES2015) Destructuring is a fancy word for unpacking elements from an array, or characters from a string, or properties from an object and assigning/reassigning them to one or more variables in a terse expression. Below are three code examples of each of these destructuring expressions just mentioned. 1. Destructuring Strings: // destructuring Strings characters into the variables a, b, and c let [a, b, c] = "foo"; console.log(a); // logs f console.log(b); // logs o console.log(c); // logs o /* Could be written let a; let b; let c; [a, b, c] = "foo"; // i.e. a = 'f', b='o', c='o' console.log(a); // logs f console.log(b); // logs o console.log(c); // logs o */ 2. Destructuring Arrays: // destructuring an Array of elements in the variables one, two, and three let [one, two, three] = [1,2,3]; console.log(one); // 1 console.log(two); // 2 console.log(three); // 3 3. Destructuring Objects: // destructuring an Object properties in the variables f and l // i.e. find the property first, assign its value to f. Find property last, assign its value to l. let { first: f, last: l } = { first: 'Bill', last: 'May' }; console.log(f); // Bill console.log(l); // May // The object is used to identify the property and new variable that will hold the properties value // Note the above code is commonly written using shorthand properties names let { first, last } = { first: 'Bill', last: 'May' }; console.log(first); // Bill console.log(last); // May // This means you omit the : f and : l, and the assignment uses first:first and last:last // But you don't have to write first:first or last:last, just first and last /* the above is just shorthand for: let { first:first, last:last } = { first: 'Bill', last: 'May' }; */ Don't over think destructuring, it simply is a terse way to take a collection of values and assign those values within the collection to different or new variables. Notes: Parentheses are required around object destructuring when the expression is assigning values (i.e. {a, b} = {a: 1, b: 2}; will throw an error but ({a, b} = {a: 1, b: 2}); will not). 4.5 : Using Destructuring With Function Arguments and Parameters (ES2015) Take what you know about destructuring and now apply it to function arguments and parameters. Basically, a function parameter can be written as a collection of identifiers that can destructure String, Array, and Object values. The result is an extremely terse way to unpack Arrays, Objects, or Strings into separate parameters using a single argument. Array arguments can be destructured: // assign argument values to identifiers in the first parameter let func = function([one, two, three]) { console.log(one, two, three); } func([1,2,3]); // Same concept as: let [one, two, three] = [1,2,3]; but using arguments & parameters instead Object arguments can be destructured (Most Common Usage): // assign argument values to identifiers in the first parameter let func = function({ first: f, last: l }) { console.log(f,l); } func({ first: 'Bill', last: 'May' }); // Same concept as: let { first: f, last: l } = { first: 'Bill', last: 'May' }; String arguments can be destructured: // assign argument values to identifiers in the first parameter let func = function([a, b, c]) { console.log(a,b,c); } func('foo'); // Same concept as: let [a, b, c] = "foo"; Destructuring function parameters are a terse way to pass complex data via a single argument and have the argument values be available in the function for immediate use without creating any assignment boilerplate in the function. Notes: Destructuring can occur on any parameter. 4.6 : Using Default Destructuring Values (ES2015) Destructuring syntax also permits the use of default values so that assignments can have fallback values. In other words if the value you are destructuring is undefined a fall back value can be setup. // Note: Fallback to default value when destructuring Strings const [a='f', b='o', c='o'] = ''; console.log(a); // log f console.log(b); // log o console.log(c); // log o // Note: Fallback to default value when destructuring Arrays const [one=1, two=2, three=3] = [undefined,44,undefined]; console.log(one); // log 1 console.log(two); // log 44 console.log(three); // log 3 // Note: Fallback to default value when destructuring Objects const { first: f = 'John', last: l = 'Doe' } = { first: 'Mary' }; console.log(f); // log Mary console.log(l); // log Doe This will of course also work with when destructuring function arguments // destructuring array argument, with default values const func1 = function([one=1, two=2, three=3]) { console.log(one, two, three); } func1([]); // destructuring object argument, with default values const func2 = function({ first: f = 'John', last: l = 'Doe' }) { console.log(f,l); } func2({}); // destructuring string argument, with default values const func3 = function([a = 'f', b = 'o', c = 'o']) { console.log(a,b,c); } func3(''); 4.7 : Using the Spread Operator (ES2015, ES2018) The spread operator (i.e. ... ) is used to unpack or expand elements from an Array, properties from an Object, or individual characters from a String, so as to immediately use these individual values in a couple of specific cases. Below I detail these cases. 1. Arrays can be spread into Array literals or when calling a function: // spreading an Array into a function call console.log(...['f', 'o', 'o']); // logs f o o, like calling console.log('f', 'o', 'o'); // spreading Array(s) into an Array literal console.log([...[1, 2], ...[3, 4, 5]]); // logs [1,2,3,4,5] Notes: Spreading Arrays into Array literals is commonly used to clone Arrays, merge Arrays into a new or old Arrays, and spread Array elements into a function call as arguments. Spreading an Array can replaces usages of .concat() . 2. Object properties can be spread into Object literals: const obj1 = {bob: true, steve: false}; const obj2 = {lisa: true, bob: false}; // spreading Objects into an object literal console.log({...obj1, ...obj2}); // logs {bob: false, steve: false, lisa: true} // Note that last bob value in wins. Notes: Spreading objects was added in ES2018. Order matters, last property and value spread wins. Spreading Objects into object literals is commonly used to clone Objects, merge objects into a new or old Objects, or filling in defaults in Objects. Spreading an Object can replace usages of Object.assign({}, obj1, obj2); . 3. Strings can be spread into Array literals or when calling a function: // spreading a string into an array console.log([...'bar']); // logs ['b','a','r'] // spreading a String into a function call console.log(...'foo'); // i.e. console.log('f','o','o'); // logs f o o 4.8 : Using the Rest Operator (ES2015, ES2018) Unfortunately, the rest operator (i.e. ... ) looks exactly like the spread operator but instead of expanding things it will wrap things up. The rest operator can be used in the following two cases: 1. When defining function parameters a rest operator can be used on the last parameter to indicate that any unidentified arguments should be wrapped up into an array. /* By place the ... operator directly in front of the last function parameter the rest of the arguments passed to a function besides the ones before the rest parameter are wrapped up into an array. */ const func = function(param1, param2, ...restOfArguments) { console.log(param1, param2, restOfArguments); } // call func with 7 parameters, but only two are define, the rest are wrapped up into an array func(1,2,3,4,5,6,7); // logs 1 2 [3, 4, 5, 6, 7] 2. When destructuring, remaining values can be wrapped up too: // destructuring String characters in the variables a, b, c, and the rest into d Array const [a, b, c, ...d] = "doggy"; console.log(a); // d console.log(b); // o console.log(c); // o console.log(d); // ["g", "y"] // Note: use of Array of variables before assignment // // logs f o o an Array elements in the variables one, two, three, and the rest in restOfNumbers of Array const [one, two, three, ...restOfNumbers] = [1, 2, 3, 4, 5, 6]; console.log(one); // 1 console.log(two); // 2 console.log(three); // 3 console.log(restOfNumbers); // [4, 5, 6] // // logs f o o an Object properties in the variables f, l, and the rest in restOfProps Object const { first: f, last: l, ...restOfProps } = { first: 'Bill', last: 'May', age: 45, living: false }; console.log(f); // Bill console.log(l); // May console.log(restOfProps); // { age: 45, living: false } Careful not to confuse the spread operator with the rest operator. The ... operator is identical but the context in which it is used changes how the operator functions. The rest operator is used when defining function parameters and destructuring. The spread operator turns iterable items into arguments for functions, into properties for Object literals, or elements for Array literals. 4.9 : Using Template Literals (ES2015, ES2016, ES2018) Characters wrapped in backticks (i.e. `string here` ) instead of quotes are considered template string literals that return a String value. Template literals support line breaks and interpolation (i.e. a template like syntax for easily inserting values into the string, using ${} ). In the code example below a multi-line string is created from the firstName and lastName variables let firstName = 'Jane'; let lastName = 'Smith'; console.log(`Hello Mr. ${lastName}! Welcome! May I call you ${firstName}?`); // Note: when log to the console line breaks are honored /* "Hello Mr. Smith! Welcome! May I call you Jane?" */ Notes: A backslash is used for escaping inside template literals (e.g. `\${}` becomes '${}' . 4.10 : Using Tagged Template Literals (ES2015) Tagged template literals are template literals that get run through a function and passed the template literal details. Commonly a String value is returned from a tag function, but any value can be returned. In the code example below the tag function verifyName will use a default name if one of the values passed to the template literal is undefined . Basically, by tagging the template literal with a tag function I make sure it will always have a first and last name even if the values passed to it are undefined. // This is a contrived example highlighting the mechanics of a tag function let name; // tag function to adjust string if name is undefined const verifyName = function(stringsFromTemplate, nameTemplateData) { const s = stringsFromTemplate; // In an array if(nameTemplateData === undefined){ return `${s[0]}${'[no name given]'}${s[1]}${'[no name given]'}${s[2]}` }else{ return `${s[0]}${nameTemplateData}${s[1]}${nameTemplateData}${s[2]}` } } // Use verifyName tagged function with undefined value let greeting1 = verifyName`Hello ${name}! May I call you ${name}?` console.log(greeting1); //logs Hello [no name given]! May I call you [no name given]? name = 'Pat'; // Use verifyName tagged function with defined value let greeting2 = verifyName`Hello ${name}! May I call you ${name}?` console.log(greeting2); //logs Hello Pat! May I call you Pat? Notes: JavaScript provides one tag function baked into the language String.Raw() . This tagged function ignores backslashes and returns the raw characters contained in the template literal (e.g. String.raw`\${}` returns '\${}' ). 4.11 : Using Fat Arrow Function Expressions (ES2015) Today, developers have replaced traditional function expressions like: const func = function (x) { return x * x; }; with a new syntax that uses a fat arrow (i.e. => ): const func = x => x * x ; // note: Omitted parenthesis around single parameter and use of implicit return. // This works fine when you have a single parameter and a single expression to return. // a less terse version of the above const func = (x) => { return x * x; }; // parenthesis and blocks only required if you have more than one parameter or expression const func = (x, y) => { const doubleY = y * 2; return z * doubleY; }; The previous code examples are similar in that they are function expressions, but the fat arrow function is not a total replacement for functions in general. Fat arrow function expressions are best suited for non-method functions, and they cannot be used as constructor functions (Arrow functions do not have prototypes). In most cases, fat arrow function expressions are used due to their terseness. Fat arrow functions can also provide another benefit besides terseness. They will take on the value of this in the context they are used instead of binding a new this value (i.e. fat error functions provide a lexical this , meaning the this value is determined based on surrounding scope at runtime). Have you ever had to hack a reference to this in the function scope chain using a that variable: // Broken because using this keyword creates a new binding in function(){} var CounterBroken = function () { this.num = 0; this.timer = setTimeout(function () { // console.log(this); //this, refers to window this.num++; // this, does not refer to CounterBroken instance console.log(`Broken this = ${this.num}`); //logs Broken this = NaN }, 1000); } var counterBrokenInstance = new CounterBroken(); // Create an Instance of CounterBroken // Fixing broken counter constructor using that = this var CounterFixed = function () { var that = this; this.num = 0; this.timer = setTimeout(function() { console.log(that.num); //that = this, from scope above that.num++; // this will now work, but a scope chain hack console.log(`Fixed this = ${that.num}`); //logs Fixed this = 1 }, 1000); } var counterInstance = new CounterFixed(); // Create an Instance of CounterFixed Using the new fat arrow function you no longer have to write var that = this or perform binding() 's. The fat arrow function provides this by default (i.e. lexically scoped this ): // Remove that = this, just use fat arrow function expression instead var CounterFixed = function () { this.num = 0; this.timer = setTimeout(() => { console.log(this.num); // this, is CounterFixed instance this.num++; // this refers to what we want now with no hack console.log(`Fixed this = ${this.num}`); }, 1000); } var counterInstance = new CounterFixed(); Notes: "An Arrow Function does not define local bindings for arguments, super, this, or new.target. Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment. " - http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions-runtime-semantics-evaluation Forging the use of the blocks in an fat arrow function expression can require the use of parentheses. (e.g. const func = () => foo(); should be written const func = () => (foo()) ; and const obj = x => { bar: x }; should be written const obj = x => ({ bar: x }); . The return keyword is implied and can be omitted when using an Arrow function (e.g. let add = (x,y) => x + y ) 4.12 : Using Trailing Commas (ES2015, ES2017) Trailing commas in the code below are considered valid JavaScript today: //Trailing comma in Array literal console.log([1, 2, 3,]) // no error //Trailing comma in Object literal console.log({bob: true, steve: false,}) // no error //Trailing comma in Function parameter definitions const func1 = ((x,) => { // no error console.log(x) })('cat'); //Trailing comma in Function arguments call const func2 = ((x) => { console.log(x) })('dog',); // no error // Trailing comma in array destructuring const [index0, index1,] = ['bee', 'flee']; // no error console.log(index0, index1); // Trailing comma in object destructuring const {bob:bobsValue, jill:jillsValue,} = {bob: false, jill: true}; // no error console.log(bobsValue,jillsValue); Notes: Trailing commas are not allowed in JSON. Pragmatically trailing commas can be useful because you don't have to adjust commas anymore when adding new elements, parameters, or properties to JavaScript code. Additionally, not throwing an error on a trailing commas makes version control diffs cleaner and less troublesome. Values using the rest operator may not have a trailing comma e.g. :



const [a, ...b,] = [1, 2, 3];



and



const func = (...p,) => {};



this will cause a SyntaxError . 4.13 : Using the for-of loop (ES2015) Commonly looping today will be done by way of an Array method like .map() or .forEach() . However, don't be surprised if you see the for-of loop in modern code. It can be used to loop over Strings, Arrays, Maps, Sets, basically anything that is an iterable value. The for-of loop goes through an iterable and assigns each entry in the iterable one at a time to the variable(s) define before the of keyword. Looping over Arrays using for-of loop: const anArray = ['a', 'b', 'c']; for (const item of anArray) { console.log(item); // logs "a", "b", "c " } Looping over Strings using for-of loop: const aString = 'cat'; for (const character of aString) { console.log(character); // logs "c", "a", "t " } Looping over Maps using for-of loop: // create a new Map called myMap and preload it with key-value entries const myMap = new Map([ ['key1', 'value1'], ['key2', 'value2'] ]); // Use for-of loop to loop over Map for (const entry of myMap){ console.log(entry); // logs: // ["key1", "value1"] // ["key2", "value2"] } As of ES2017 one can use Object.entries() and destructuring with the for-of loop to loop over Object literals.: const arr = ['zero', 'one']; for (const [index, element] of arr.entries()) { console.log(index, element); } // above logs 0 "zero" // above logs 1 "one" Notes: The for-of loop provides similar terseness found with Array looping methods but also supports break and continue . 4.14 : Using Shorthand Property Names (ES2015) When defining an object literal if the colon and value are omitted the value from a similarly named variable within the same scope will be used. In the code below the const myNumber value is used for the myNumber property value in the myObjectLiteral object. const myNumber = 1; const myObjectLiteral = { myNumber, // notice all I put here was a property name // the above is shorthand for myNumber : myNumber }; console.log(myObjectLiteral); // Logs Object {myNumber: 1} Notes: Shorthand property names are often used in combination with destructuring. 4.15 : Using Shorthand Method Names (ES2015) When adding methods (i.e. functions) to object literals a new terser syntax is possible. This is the old non-short syntax: const myObjectLiteral = { myMethod: function(parameters){ return; } }; This is the new shorthand syntax. const myObjectLiteral = { myMethod(parameters){ return; } }; Which saves you from having to write, : function . Note how similar this new syntax is to getters and setters from ES5: const myObjectLiteralWithGetterAndSetter = { set myMethod(parameters){ return; }, get myMethod(){ return; }, }; 4.16 : Using Computed Property Names (ES2015) Using brackets around property names/keys will permit the name/key to be a result of an expression. const prop = 'prop'; // define an object literal with name/keys that are the result of an experssion const myObject = { // note property keys/names are computed [prop + 1]: 1, [prop + '2']: 2 } console.log(myObject); /* logs [object Object] { prop1: 1, prop2: 2 } */ Notes: This is similar to the bracket notation that can be used to access a key/property on an object from a computed property name.

Chapter 5 : ES2015 Map() and Set() This chapter will cover usages for the new Map() and Set() objects. 5.1 : Using Maps instead of Object Literals (ES2015) ES2015 comes with Map() . Maps are key-value pairs stored in insertion order. Sounds a lot like an object right (i.e. {key1:value1, key2:value2} )? Think of Maps as objects but with a built-in native API for working with the object and its key-value pairs. Examine the code below to gain a basic overview of the creation and usage of a Map . Note that most of the built in methods for Map() are demonstrated (e.g. clear() , entries() , forEach() , get() , has() , keys() , set() , values() ). // create a new Map called myMap // Preload myMap with key-value entries, as an Array, contained in an Array const myMap = new Map([ ['key1', 'value1'], ['key2', 'value2'] ]); // .forEach() // loop over each entry in the Map using Map's forEach() method myMap.forEach((value, key) => { console.log(`${key} = ${value}`); }); // logs "key1 = value1 " "key2 = value2 " // .entries() // List of all key-value pairs, spreading into console log console.log(...myMap.entries()) // logs ["key1", "value1"] ["key2", "value2"] // .keys() // List of all keys, spreading into console log console.log(...myMap.keys()) // logs "key1" "key2 " // .values() // List of all values, spreading into console log console.log(...myMap.values()) // logs "value1" "value2 " // .set() // Set a key-value pair myMap.set('key3', 'value3'); // .get() // Get a key-value pair console.log(myMap.get('key3')); // logs "value3" // .has() // Does a key have a value yet console.log(myMap.has('key3')); // logs true // .delete() //Delete a key-value pair, by key myMap.delete('key3') console.log(myMap.has('key3')) // logs false // .clear() //Clear all key-value pairs myMap.clear(); // .size() //Get current size of map (i.e. number of entires) console.log(myMap.size) // logs 0, because you just cleared the Map of entries Don't forget when using a Map() the key can be any value: const myMap = new Map([ ['key1', 'some value for key 1'], [2, 2], [[1], [1,2,3]], [{id:1}, {prop1:'value',prop2:'value'}], ]); myMap.forEach( (value, key) => { console.log(`${key} = ${value}`); // logs: // // "key1=s ome value for key 1" // "2=2" // "1=1,2,3" // "[object Object]=[ object Object]" }); Notes: Until ES2015 Object literals we're the substitute for a Map. Obviously a real Map provides more features and out of the box methods for working with key-value pairs. Maps can be looped over using the for-of loop. The key and the value can both be any type of value, unlike Object literals in which the key is typically a string. When should you use Maps over object literals? Use a Map when you are performing a lot of work on the entries and the built in methods make everything simpler ( clear() , entries() , forEach() , get() , has() , keys() , set() , values() ). The .set() method returns the Map. Thus, .set() can be chained (i.e. myMap.set(key,value).add(key,value); ). If you want to use Array methods on a Map simply spread the map into an array (i.e. [...myMap].filter(...); ). A narrow version of Map() is also available called weakMap() . A weakMap() , mainly differs from a Map() in that it can only hold values that are objects and when a value is removed from a weakMap() , and it has no other references, the value will immediately be garbage collected (aka weak references). Additionally a weakMap() can't be iterated over, uses .length to get size of the map, and only has set() , delete() , get() , and has() methods. 5.2 : Using Sets to create Arrays, with no Duplicates (ES2015) ES2015 comes with Set() . Sets are values stored in a specific order that can't contain a duplicate. Sounds a bit like an Array object right (i.e. [value, value] )? Think of Sets as Arrays but with a built in API for working with the items in the Array and the bonus of automatically eliminating duplicates. Examine the code below to gain a basic overview of the creation and usage of a Set. Note that most of the built in methods for Set() are demonstrated (e.g. clear() , entries() , forEach() , has() , keys() , add() , values() ). // create a new Set called mySet and preload it with values using an array/iterable // Note: that duplicates are removed when creating new Sets and adding values const mySet = new Set(['one', 'two', 'three', 'three']); // .forEach() // loop over each value in the Set using Set's forEach() method mySet.forEach((value) => console.log(value)); // logs "one" "two" "three" // .entries() // Note this uses each value as both the key and the value. // List of all value entries, spreading into console log console.log(...mySet.entries()) // logs ["one", "one"] ["two", "two"] etc... // .keys() // List of all keys (same func as values()), spreading into console log console.log(...mySet.keys()) // logs "one" "two" "three" // .values() // List of all values, spreading into console log console.log(...mySet.values()) // logs "one" "two" "three" // .has() // Does a Set have a specific value console.log(mySet.has('three')); // logs true // .add() // Add 'four' and 'three' to the Set. console.log(...mySet.add('three')); // logs "one" "two" "three" console.log(...mySet.add('four')); // logs "one" "two" "three" "four" // Note adding 'three' to a Set will not create a duplicate, as 'three' is a duplicate // .delete() //Delete a value mySet.delete('four') console.log(mySet.has('four')) // logs false // .clear() //Clear all ordered values mySet.clear(); // .size() //Get current size of Set (i.e. number of values) console.log(mySet.size) // logs 0, because you just cleared the Set of values Notes: The .add() method returns the Set. Thus, .add() can be chained (i.e. mySet.add('one').add('two'); ). If you want to use Array methods on a Set simply spread the set into an array (i.e. [...mySet].filter(...); ). A narrow version of Set() is also available called weakSet() . A weakSet() , mainly differs from a Set() in that it can only hold values that are objects and when a value is removed from a weakSet() , and it has no other references, the value will be garbage collected (aka weak references). Additionally a weakSet() can't be iterated over, uses .length to get size of the set, and only has add() , delete() , and has() methods.

Chapter 6 : ES2015 Class Syntax This chapter will discuss the ES2015 class syntactical sugar which conceals JavaScripts clunky object inheritance model. 6.1 : What class Syntax Conceals ES2105 class syntax is a cleaner way to work with prototypal inheritance and object factories. The class , extend , constructor , super , and static keywords simplify and conceal what was already possible with JavaScript. Before class syntax was available working with object constructors and prototypal inheritance, to mimic class's from other OOP languages, was done in the following way: // Create a Human class, i.e. an object factory for Human instances function Human(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // Instances created from Human constructor have a fullName method Human.prototype.fullName = function () { return `${this.firstName} ${this.lastName}`; }; // Create a Developer class function Developer(firstName, lastName, type) { Human.call(this, firstName, lastName); this.type = type; } // Have Developer inherit from Human, by creating an object that inherits from Humans prototype Developer.prototype = Object.create(Human.prototype); // Make the constructor property point at Developer, not Human Developer.prototype.constructor = Developer; // Instances created from Developer constructor have a fullNameAndLanguage method Developer.prototype.fullNameAndLanguage = function () { return `${Human.prototype.fullName.call(this)} develops ${this.type}`; }; // Add static helper function to Developer Developer.isJSdev = function(cody){ return cody.language.toLowerCase() === 'javascript'; }; // Create an instance of Developer const cody = new Developer('Cody', 'Lindley', 'JavaScript'); // Call fullNameAndLanguage() method console.log(cody.fullNameAndLanguage()); // logs "Cody Lindley develops JavaScript" // Call fullName() method console.log(cody.fullName()); // logs "Cody Lindley" // Call static type of Developer console.log(Developer.isJSdev(cody)); // logs true Using ES2015 class syntax the above can be re-written like so: // Create a Human class, i.e. an object factory class Human { constructor (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // Instances created from Human constructor have a fullName method fullName(){ return `${this.firstName} ${this.lastName}`; } } // Create a Developer class class Developer extends Human { // Have Developer inherit from Human constructor (firstName, lastName, type) { super(firstName, lastName); // call Human constructor but in this context this.type = type; } // Instances created from Developer constructor have a fullNameAndLanguage method fullNameAndLanguage(){ return `${super.fullName()} develops ${this.type}`; } // Add static helper function to Developer static isJSdev (cody){ return cody.language.toLowerCase() === 'javascript'; } } // Create an instance of Developer let cody = new Developer('Cody', 'Lindley', 'JavaScript'); // Call fullNameAndLanguage() method console.log(cody.fullNameAndLanguage()); // logs "Cody Lindley develops JavaScript" // Call fullName() method console.log(cody.fullName()); // logs "Cody Lindley" // Call static type of Developer console.log(Developer.isJSdev(cody)); // logs true The details of a class object will be broken down in this chapter. For now, you should observe from the newer class syntax the following: The use of the class , extend , constructor , super , and static keywords that were added to JavaScript to simplify prototypal inheritance for object-oriented minded developers (i.e. cleaner, concealing prototypal nuances, less boilerplate). The use of shorthand method names within the class object. Which is the only option! No commas separating the constructor function, method functions, or static method functions in the class object. Notice how prototype boilerplate is eliminated and referencing an inherited class is trivial using super . 6.2 : The class Expression v.s. class Declaration A class can be defined using either a class declaration or class expression. A class declaration: class Human { constructor (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // class name can be used to reference the class inside the class object classMethod (){ console.log(Human); // logs out class object backing property function } } const jill = new Human(); // call classMethod and log to the console reference to class, from inside of the class jill.classMethod(); A class expression: const Human = class optionalClassNameForReferenceInClassMethods { constructor (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // the optional name can be used to reference the class inside the class object classMethod (){ // logs out class object backing property function console.log(optionalClassNameForReferenceInClassMethods); } } const jill = new Human(); // call classMethod and log to the console reference to class, from inside of the class jill.classMethod(); Notes: Class declarations are not hoisted. Both class declarations and class expression bodies are executed in strict mode. A class declaration can be declared once and any other declarations will throw a type error. A class expression can be re-defined to another class expression but not a class declaration. Doing so will also throw a type error. A class can not be called without the new keyword. When using class syntax the prototype of the class (e.g. Human.prototype ) is read-only. Class definitions are first class citizens (i.e. you can pass them to functions, return a class from a function, and assigned them to variables). 6.3 : Using a Class constructor Method Each class definition can have exactly one constructor function that is invoked when an instance of the class is instantiated. Typically, the constructor function will define the initial properties for instances created from the class. // create a Human class that expects a first and last name value when instantiated class Human { constructor (firstName, lastName) { console.log('constructing'); // the this keyword, used below, is the new object create when calling Human this.firstName = firstName; // create a firstName property and give it a value this.lastName = lastName; // create a lastName property and give it a value } } const may = new Human('May','Jones'); // logs 'constructing' console.log(may); /* logs: [object Object] { firstName: "May", lastName: "Jones" } */ Notes: A constructor function is optional. If you omit one, a blank constructor is created for you (i.e. constructor(){} ). The keyword super() , within the constructor is used to call the parent class (i.e. the class, a class inherits or is extend 'ed from). 6.4 : Using a Class Method Class methods can be called on instances of the class. When called, the method of the class uses the values defined on the instance via the this keyword. // create a Human class that is passed a first and last name value when instantiated class Human { constructor (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // Instances created from Human class have a fullName class method fullName(){ // this inside a class method refers to the instance the method is called on return `${this.firstName} ${this.lastName}`; } } const may = new Human('May','Jones'); // may is an instance of the Human class const bill = new Human('Bill','Jones'); // bill is an instance of the Human class console.log(may.fullName()); // logs "May Jones" console.log(bill.fullName()); // logs "Bill Jones" The fullName class method is defined once for all instances. Notes: Class methods are non-enumerable by default. Class methods are written using the shorthand method naming. The keyword super.[class method] within a class method can reference and or call the methods of the parent class (i.e. the class, a class inherits or is extend 'ed from). Getters and Setters (aka Accessors descriptors or computed properties) can be used on class methods (i.e. class MyClass { get MyMethod(){...} set MyMethod(x){...} } ). Computed property names can be used within class definitions (i.e. ['method'+'Name'](){ ... }) . 6.5 : Using a static Class Method While class methods are called on instances of a class, static class methods are called from the class itself. Don't over think this setup. This is the different between something like Array.isArray() and [].forEach() . Array.isArray() is a static method on the Array "class" itself while [].forEach() is a "class" method called on instances of an Array // Define a Human class with a static method class Human { static isHuman(classInstance){ // is the constructor of the classInstance the same as this class i.e. Human = Human, return classInstance.constructor === this; } } // create an instance of the Human class var pat = new Human(); // call static method on Human class Human.isHuman(pat); // logs true Notes: Static methods are also inherited. From within a static method, you can refer to other static methods with this.staticMethodName . But inside a constructor or class method you will have to either use ClassName.staticMethodName or this.constructor.staticMethodName . 6.6 : Using extend to Inherit Methods from Another Class A class can be sub-classed (i.e. Human > Developer) when using the extend keyword upon definition. For example, in the code below when defining the Developer class we use the extend keyword to link/inherit the parent Human class to the child Developer class. // Create a Human class, i.e. an object factory class Human { constructor (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // Instances created from Human constructor have a fullName method fullName(){ return `${this.firstName} ${this.lastName}`; } } // Create a Developer class class Developer extends Human { constructor (firstName, lastName, type) { super(firstName, lastName); // like calling Human.call(this, firstName, lastName); this.type = type; } } // Create an instance of Developer let cody = new Developer('Cody', 'Lindley', 'JavaScript'); // Call fullName() method, inherited from Human Class console.log(cody.fullName()); // logs "Cody Lindley" Notes: Built-in objects can be extended or sub-classed not just user-defined classes. This inheritance link gives scope access to Human from Developer using the keyword super . If a sub-class is missing a constructor, the parent constructor is called. If a sub-class does have a constructor, it will need to call super() with the expected arguments to call the parent's constructor. 6.7 : Using super to Call the Inherited Constructor The super keyword within a constructor method is used to invoke the parent class from the child class. This invocation setups the needed properties for inherited methods to run correctly. // Create a Human class, that other class's will use as a parent class class Human { constructor (firstName, lastName) { console.log('Super() called this constructor from child Class'); this.firstName = firstName; this.lastName = lastName; } // Instances created from Human constructor have a fullName method fullName(){ return `${this.firstName} ${this.lastName}`; } } // Create a Developer class, that is a child class of Human class Developer extends Human { constructor (firstName, lastName, type) { // super has to be used first, if used super(firstName, lastName); // like calling Human.call(this, firstName, lastName); // the above, using a parent constructor, does this: // this.firstName = firstName; // this.lastName = lastName; this.type = type; } } // Create a Lawyer class, that is a child class of Human class Lawyer extends Human { constructor (firstName, lastName, type) { // super has to be used first, if used super(firstName, lastName); // like calling Human.call(this, firstName, lastName); // the above, using a parent constructor, does this: // this.firstName = firstName; // this.lastName = lastName; this.type = type; } } // Create an instance of Developer, both the Developer and Human constructors are invoked. const cody = new Developer('Cody', 'Lindley', 'JavaScript'); console.log(cody.fullName()) // works because Developer has a firstName and lastName setup by calling super() // Create an instance of Lawyer, both the Lawyer and Human constructors are invoked. const lisa = new Lawyer('Lisa', 'Lindley', 'Criminal'); console.log(lisa.fullName()) // works because Lawyer has a firstName and lastName setup by calling super() Notes: The super keyword should be the first expression in a constructor function (i.e. before the this keyword is used). 6.8 : Using super to reference Inherited Methods The super keyword when used within a class method or static method will be a reference to the parent class's methods. class Human { constructor (firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } fullName(){ return `${this.firstName} ${this.lastName}`; } } class Developer extends Human { // Have Developer inherit from Human constructor (firstName, lastName, type) { super(firstName, lastName); this.type = type; } fullNameAndLanguage(){ // use super keyword to reference parent class method, and invoke it. return `${super.fullName()} develops ${this.type}`; // Note referencing super alone will throw an error i.e. console.log(super); } } // Create an instance of Developer const cody = new Developer('Cody', 'Lindley', 'JavaScript'); // Call fullNameAndLanguage() method console.log(cody.fullNameAndLanguage()); // logs "Cody Lindley develops JavaScript" 6.9 : Extending JavaScript Built-in's and Web API Constructors/Classes using class Syntax. Before ES2015 class syntax sub-classing/extending a built in constructor/class like Array() had significant limitations. Today, extending the JavaScript Array() class is trivial using class syntax. class MyCustomArray extends Array{ customEntriesMethod(){ return Object.entries(this); } } let myCustomArrayInstance = new MyCustomArray('one', 'two', 'three'); myCustomArrayInstance.push('four'); console.log(myCustomArrayInstance.customEntriesMethod()); // logs [["0", "one"], ["1", "two"], ["2", "three"], ["3", "four"]] Even, JavaScript web API's like the DOM can be extended. Notes: If you run the above code through Babel is will break. Sub-classing constructors/classes has to be supported natively. Transpiling and polyfilling can't help and in fact, will break the code. The environment either supports extending built-in classes/object or it does not. If you extend natives make sure you are not also babel'ifying/transpiling the code.

Chapter 7 : ES2015 Promises This chapter will examine the need for JavaScript Promises and then explain their usage. 7.1 : Asynchronous Programming Basics Code that does not run completely as part of a normal execution cycle is said to run asynchronously. Asynchronously executed code in this context basically means that one defines code now to occur later in the future while not blocking other code from running synchronously. The simplest example of this is setTimeout() . // execute the passed function in 10'ish seconds in the future setTimeout(() => console.log('Ten seconds ago, you ask me to run this code'), 10000); // But keep executing code, don't wait for 10secs, and block all code execution console.log('I am not delayed by 10 seconds from running'); The function passed to setTimeout() is known as an asynchronous callback function. This function will run 10'ish seconds in the future without blocking other code from running. Consider that the setTimeout() function argument is not all that different from a callback function for a click event or a callback function used to capture the response from an XMLHttpRequest network request. Basically, when you ask JavaScript to wait to run some code, while not blocking the rest of the program from running, you are dealing with asynchronous code. Notes: An in-depth understanding of asynchronous programming in JavaScript requires an understanding of the call stack and the event-loop. Here is a simple and concise review of these parts, "Help, I'm stuck in an event-loop.". If you've struggled in the past with understanding promises it is likly because you lack the foundational information found in the aforementioned video. 7.2 : Asynchronous Programming Before ES2015 (Or, Why Promises?) Callback functions before ES2015 were the typical means in which one dealt with asynchronous programming situations. And even today, a simple callback function works just fine in many situations (e.g. the user clicks on a button and the corresponding callback function is run asynchronously). However, asynchronous situations do exits that make using callback functions buggy and laborious. These situations can involve complex network exchanges found between a client and a server. Imagine you need to make five different requests to the server from the client and the requests have a complex relationship with each other. For example, imagine that the first request has to finish before the second and third request can begin. And both the second and third request have to finish before the fourth and fifth request can begin. And, the fourth and fifth request are not both required to be finished, you only need one to finish, whichever one is first. Also imagine, for each request you have to create an error handling situation. I hope you can see that using callbacks alone, to complete the task just mentioned, using something like the XMLHttpRequest web API will either lead to a pyramid of callback functions: // A function within a function within a function within a function etc... getData1(function(x){ ... getMoreData2(x, function(y){ ... getMoreData3(y, function(z){ // on and on it could go }); }); })(); // oh no the pyramid of doom! Or, a long list of linked callback functions: // function 1 calls function 2, function 2 calls function 3 etc... getData1(function(x){ ... getMoreData2(x) })(); getData2(function(y){ ... getMoreData3(y) }); getData3(function(z){ // on and on it could go }); Either way, many consider this situation, callback hell. To combated the complexity of complicated asynchronous programming and avoid callback hell ES2015 provided the native Promise API as an alternative to callback functions alone. The remainder of this chapter will cover the new Promise API in further detail. Notes: Originally, promises were called "Futures" and were a part of the DOM spec. Thankfully, in the end it ended up in the ECMAScript specification so all JavaScript runtimes could use Promises for asynchronous programming. Unfortunately, IE does not support promises and it will have to be polyfilled if you need IE support. However the Edge browser (12+) does have support. 7.3 : Producing & Consuming a Promise An instance of a Promise is basically a function that typically houses asynchronous routines with two special functions to be called once the asynchronous work either completes or fails (i.e. resolve() or reject() ). In short, a Promise provides the boilerplate around asynchronous code which results in an interface/methods for working with asynchronous code. Consider the methods and static methods provided by Promises: Promise.all()

Promise.prototype.then()

Promise.prototype.catch()

Promise.prototype.finally() (ES2018)

(ES2018) Promise.race()

Promise.reject()

Promise.resolve() These methods and static methods provide a cleaner API when dealing with complicated relationships among asynchronous code. To produce a promise one only need to construct an instance of a promise from the Promise constructor passing the constructor one argument known as the executor function. The executor function is passed two argument functions, the first is called resolve , and the second argument is called reject . // Create a Promise called myPromise const myPromise = new Promise( (resolve, reject) => { // this is an executor function try{ // do some async work, like an XHR request or a setTimeout() // ... // then call resolve() when done, pass it some data setTimeout(function(){resolve('foo');}, 1000); // comment out the line above and un-comment line below to see error thrown // foo; }catch(error){ // if an error occurred, call reject() reject(error); } } ); // Consume myPromise myPromise.then( // takes two functions, // if promise calls resolve() this first function is called (data) => { console.log(data); }, // logs 'foo' // if promise calls reject() this second function is called (error) => { console.log(error.toString()) } // logs Error ); Let's take the promise API for a spin. In the code example below I am wrapping a Promise around the older XMLHttpRequest object and concealing its older callback function API in order to create a mini Github Promise based API. // Create a function that will return a Promise const getGitHubData = (gitHubRESTPath) => { // return a Promise so the .then() method can be called on returned value return new Promise((resolve, reject) => { // this is the executer function // start asynchronous work const xhr = new XMLHttpRequest(); xhr.onload = function () { // callback function if (this.status === 200) { // call resolve() with async results when async work is done resolve(this.response); } else { // call reject() with statusText if server returns anything but a 200 reject(this.statusText); } }; xhr.onerror = function () { // callback function // call reject() with error message if XHR errors occur reject('XHR Error:'); }; xhr.open('GET', 'https://api.github.com/' + gitHubRESTPath); xhr.send(); }); }; // Now call getGitHubData() and consumer the Promise wrapped around XMLHttpRequest // Get number of stars for React on github getGitHubData('repos/facebook/react').then( // use then() to consume the eventual results //resolve function (response) => { // convert the JSON string response to a JS object console.log(JSON.parse(response).stargazers_count); // logs 11XXXX }, //reject function (error) => { console.log(error); } ); // Send a bad URL, to see reject function run getGitHubData('repos/nothing/nothing').then( // use then() to consume the eventual results //resolve function (response) => { // convert the JSON string response to a JS object console.log(JSON.parse(response).stargazers_count); }, //reject function (error) => { console.log(error); // logs 'Not Found' } ); By wrapping the Promise itself with the getGitHubData function I can call the getGitHubData function like an API. The "mini api" provides a cleaner interface for working with asynchronous HTTP requests to the Github REST API because a Promise is used/returned. To consume the returned promise the then() method is used to capture both the resolution of the asynchronous code and potentially any resulting errors (i.e. then() takes two arguments. First argument is resolution function, called when promise is resolved, second is a rejection function, called if the promise is rejected). Notes: The executor function pass to Promise() is called before the Promise constructor returns the created object. If an error is thrown in the executor function the promise is rejected. Promises have three states or life cycles (pending > resolved || rejected > settled). The promise starts in a pending state. Then it either moves to a fulfilled (i.e. resolved) or rejected state. Once in a fulfilled or rejected state the promise is considered to be settled. Once settled, a promise can't change its state. Settled means, settled. One should consider a Promise as a stand-in for a value that has yet to be determined. In short, you use a promise to wrap asynchronous routines and then use promise methods to provide callback functions for the routines. 7.4 : Consuming Promises Since promises are native to the language several JavaScript runtimes can make use of Promises. For example, the new Fetch API, which replaces the older XMLHttpRequest API returns a promise by default. Thus, instead of having to create a promise, all you have to do is consume the promise returned from calling fetch() . I've re-written the code from the previous section to use the new Fetch API. By using the Fetch API I don't need to produce a Promise object manually, one is simply given to me by the web platforms Fetch API. As you can see in the code example below when a promise is returned all that is left to do is to consume the promise using promise methods (e.g. then() method) // Get number of stars for React on github fetch('https://api.github.com/repos/facebook/react') .then( // use then() to consume the eventual results //resolve function (response) => { // note that calling response.json() is itself a promise response.json().then(json => console.log(json.stargazers_count)); // logs 11XXXX }, //reject function (error) => { console.log(error.toString()); // logs "undefined" } ); // Send a bad URL, to see reject function run fetch('htttps://www.badnogoodurl.com') .then( // use then() to consume the eventual results //resolve function (response) => { // note that calling response.json() is itself a promise response.json().then(json => console.log(json.stargazers_count)); // logs 11XXXX }, //reject function (error) => { console.log(error.toString()); // log "undefined" } ); 7.5 : Producing an already Resolved or Rejected Promise The Promise API offers the Promise.resolve() and Promise.reject() static methods that will produce an instance of a Promise that has either been resolved or rejected with a given value. This can be handy when needing to quickly create a promise in a specific state without dealing with an executor function. The Promise.resolve() static method returns a resolved promise with a specific value passed to it: Promise.resolve('value1') .then(value => console.log(value)); // logs "value1" // The above is a shortcut for this: new Promise(resolve => resolve('value2')) .then(value => console.log(value)); // logs "value2" The Promise.reject() static method returns a rejected promise with a specific value passed to it: Promise.reject('error1') .then(() => {}, error => console.log(error)); // logs "error1" // The above is a shortcut for this: new Promise((resolve, reject) => {reject('error2');}) .then(() => {}, error => console.log(error)); //logs "error2" Don't over think these two static methods they simply side step the executor function and return a promise in a specific state. Notes: The Promise.resolve() static method is typically used to wrap a Promise around a value. 7.6 : Chaining Promises with then() The then() method is used to provide a resolution and rejection function for a promise. Remember, a promise runs/contains asynchronous code. This asynchronous code when complete can call one of two functions either resolve() or reject() . The then() method accounts for both of these calls. If resolve() is called then the first function to then() is invoked. If reject() is called then the second function pass to then() is called. In the code example below I am using then() to extract the commits on Github for React, then extract the author of the last commit, then log that author to the console. // get the name of the last person to commit to the React github repository // fetch all commits for React fetch('https://api.github.com/repos/facebook/react/commits') // handle response, pass json to next then() .then(response => response.json(), error => console.log(error)) // fetch user from Github API .then((json) => { // use commit data to get user name of last person to commit // use user name to get author data // pass response to next then() return fetch('https://api.github.com/users/'+json[0].author.login); }, error => console.log(error)) // handle response pass json to next then() .then(response => response.json(), error => console.log(error)) // get name, pass it to the next then(), note I am not passing a promise but just a simple value .then(user => user.name, error => console.log(error)) // log the name of the last person to commit code to the react repository .then(name => console.log(name + ' is the last person to commit to React repository')); It is important to remember, and you may have noticed in the previous code example, that the then() method can be used to chain any value not just promises. In the code example below I demonstrate using the promise API to simply transfer data through a promise chain. // create a promise that is already resolved with a specific 'foo' value. var myPromise = Promise.resolve('foo'); // show how a promise can chain any value indefinitely. myPromise.then((data)=>{ return data; }).then((data)=>{ return data; }).then((data)=>{ console.log(data); // logs 'foo' }); However, the then() method was specifically designed to chain promises that are doing asynchronous activities. In the following code example I use the promise chain to verify all the starwars API endpoints are functioning. let starWarsAPICheck = []; console.log('Wait for it...! Data from another Galaxy yo!'); // chain a set of promise to occur one after the other // check the starwars api to see if all endpoints are working. fetch('https://swapi.co/api/people') .then((response)=>{ starWarsAPICheck.push(response.ok); return fetch('https://swapi.co/api/planets'); }).then((response)=>{ starWarsAPICheck.push(response.ok); return fetch('https://swapi.co/api/films'); }).then((response)=>{ starWarsAPICheck.push(response.ok); return fetch('https://swapi.co/api/species'); }).then((response)=>{ starWarsAPICheck.push(response.ok); return fetch('https://swapi.co/api/vehicles'); }).then((response)=>{ starWarsAPICheck.push(response.ok); return fetch('https://swapi.co/api/starships'); }).then(()=>{ console.log(starWarsAPICheck); // logs [true, true, true, true, true, true] }); // Note the previous chain all happen sequentially // To make all the calls run in parallel, Promise.all() could be used. One should note that any error returned within the first function passed to then() will result in the next then() running the rejection function (i.e. the second function padded to then() ). // create a promise that is already resolved with a specific a 'foo' value. var myPromise = Promise.resolve('foo'); // show how error returned in the second then(), calls reject callback function, but chain goes on myPromise.then((data)=>{ return data; }).then((data)=>{ throw Error('yo this then() is no good'); // will cause reject callback in next then() to run }).then( (data)=>{console.log(data)}, // does not run (error)=>{console.log(error.toString())}, //logs 'yo this then() is no good' ).then((data)=>{ console.log('note this still runs'); console.log(data); // logs undefined, foo is undefined }); Basically, using then() allows a developer to push data through a set of functions that can deal with both synchronous and asynchronous results interchangeable. When an error occurs, the chain does not stop, the next then() simply captures the error and runs the second function passed to it. Notes: If you don't provide then() a resolve function or provide a non-function, no error will occur. The next then() resolve function is passed an undefined value. 7.7 : Catching Rejections in the Chain with catch() When rejection errors occur within a promise chain the catch() method can be used to capture the rejection. This can be handy for two reasons. First off, you can write a single error handle for a long list of chained promises and the first rejection in the chain will get passed on to the catch() . Secondly, because catch() returns a promise, just like then() , you can keep chaining with more then() 's or catch() 's. Basically, one could perform several asynchronous routines and use one catch() if any errors occur, then continue on with more then() 's or catch() 's' at will. In the code example below a broken API call is kicked off and a catch() method at the end of the chain captures the error. fetch('https://api.github.com/repos/fanebook/react/commits') // broken URL, facebook not fanebook .then(response => response.json()) .then((json) => { // this throws an error because the json passed in is bad // it is bad because the original URL is wrong facebook, not fanebook // so json[0].author.login is a bad reference // so this function throws an error which is caught in the first catch() in the chain return fetch('https://api.github.com/users/'+json[0].author.login); }) .then(response => response.json()) // response.json() returns a promise .then(data => console.log(data.name + ' is the last person to commit to React repository')) // never logs .catch(error => { // any error issue in the previous then()'s are trapped here console.log(error.toString()); }) .then(() => { console.log('yup you can still chain after a catch()'); }); Notes: The catch() method is typically used over providing each then() with a rejection parameter. Notice how readable the code in this section becomes once a single rejection function was used via a catch() over providing a rejection function for each then() . The catch() method is simply a short-hand for, .then(null, error => { // do something with error }) . 7.8 : Running a Final Function, Regardless of Promise Fulfillment State with finally() When a situation exists that you'd like to run a function regardless of if a promise is resolved or rejected you can use