Skip to content

Instantly share code, notes, and snippets.

@wktdev
Created August 14, 2018 17:18
Show Gist options
  • Save wktdev/4343261b4e3cf5f07d17d79945a9d72b to your computer and use it in GitHub Desktop.
Save wktdev/4343261b4e3cf5f07d17d79945a9d72b to your computer and use it in GitHub Desktop.
# ECMAScript 6 (ES6)
The technical name of the JavaScript language is ECMAScript. ECMA stands for the European Computer Manufacturers Association. This is the organization that oversees the standardization process of the JavaScript language.ECMAScript version 6 (ES6 for short) is a newer version of JavaScript. In this module, you will learn about some of the features of ES6 including:
* Imports/Exports
* Default function arguments
* The let and const keywords
* Template literals
* The rest parameter
* The spread operator
* Destructuring
* Set
* Weak Set
* Map
* Weak Map
* Arrow functions
* The class keyword
* Promises
* Symbols (JavaScripts 7th data type)
* Iterables and iterators
* Generators
To explore a full list of ES6 features, go to this link: http://es6-features.org
## Backward Compatibility
When developing websites that use ES6 features you will have to compensate for older browsers that don't support these features. The solution to this problem is to use a tool called a transpiler. JavaScript transpilers convert newer versions of JavaScript to older versions of JavaScript and insure that the code executes properly on older browsers. A transpiler requires the setup of an independent web server and this is beyond the scope of this module.
## Imports / Exports
**items.js**
```
export function greeting(){
return " hi there "
}
export const name = "Bob Whatever"
export let names = ["Bob","Joe","Cindy"]
export default "the default content"
```
**app.js**
```
import content, {greeting, name, names} from './items';
```
## Default Function Arguments
In ES6 default function arguments are available. To use them you assign variables in the parameter values of your functions.
```
"use strict";
function addValues(a = 1, b = 2, c = 3){ // Assign variables
return a + b + c // Defaults to 1 + 2 + 3
}
console.log(addValues()); // 6
console.log(addValues(5, 5)); // (5 + 5 + 3) === 13
```
If you invoke a function and set an argument value to undefined then the default parameter will be used.
```
"use strict";
function addValues(a = 1, b = 2, c = 3){
return a + b + c
}
console.log(addValues(2, undefined, 2)); // (2 + 2 + 2) === 6
```
## Block Scope Using let
In previous modules, you learned that JavaScript has global and function scope. This is true for ES5. ES6 has block scoped variables using the ```let``` and ```const``` keywords. You might be wondering why we didn't mention this earlier. The reason for learning ES5 first is because it is the JavaScript version most used in production and it is better to learn it first prior to moving to ES6.
### How the let Keyword Works
A block scoped variable can be created by prefacing the variable with ```let```instead of ```var```. The word "block" refers to the curley braces of a programming construct such as an if statement, a for-loop or function.The following code demonstrates a block scoped variable inside of an if statement using the ```let``` keyword.
```
"use strict";
let isTrue = true;
let someData = "blah";
if(isTrue){
let someData = "different data"; /* This does not change the variable of the same name outside the block */
console.log(someData) // different data
}
console.log(someData); // blah
```
The same code using the ```var``` keyword references the variable outside of the if statement.
```
"use strict";
var isTrue = true;
var someData = "blah";
if(isTrue){
var someData = "different data"; /* References the variable outside the block - and changes it */
}
console.log(someData); // different data
```
### Using let Prevents Accidental Variable Overwrites
If you declare the same variable using ```var``` more than once in the same scope the variable is over written. If you do the same thing using ```let``` an error is thrown. This is a good thing because it acts as a warning that a variable of the same name has already been declared in the current scope.
```
var stuff = "Some stuff";
var stuff = "Some stuff"; //overwrites previous variable named "stuff"
let moreStuff = "more stuff";
let moreStuff = "some more stuff"; // Error! Already declared!
```
Variables declared with ```let``` are reassignable.
```
let moreStuff = "more stuff";
moreStuff = "more stuff"; // no error, works just like var
```
### The const Keyword
Like the ```let``` keyword the ```const``` keyword (short for *constant*) creates block-scoped variables. The difference between ```let``` and ```const``` is that ```const``` declared variables cannot be reassigned.
Here is an example:
```
"use strict";
const someData = "blah";
console.log(someData); // blah
someData = "something else"; // Error! Cannot be reassigned
```
Prefacing your variables with the ```const``` keyword is a good idea when they should not be changed. An example is the value pi.
```
const pi = Math.PI;
```
### Template Literals
Template literals let you embed variable data in strings. To use a template literal you replace the single or double quotes of a convention string with backticks and place a variable in curley braces preceded by a dollar sign.
```
"use strict";
let user = "Cindy";
let greetUser = `Hello ${user}`;
console.log(greetUser) // Hello Cindy
```
### The Rest Parameter
The rest parameter allows you to access an undefined number of arguments in a function as a collection in an array. The syntax is expressed as three period dots followed by a name. The following code demonstrates how to use the parameter:
```
"use strict";
function stuff(...args){ // Three dots and a name (args)
console.log(args); // log name
}
stuff(1,2,3)// [1, 2, 3] is logged to the console
```
If you define arguments before the rest operator they will not be included in the array. Here is an example:
```
"use strict";
function add(a,b,...args){
console.log(args);
return a + b;
}
add(1,2,3)// [ 3 ] is logged to the console
```
### The Spread Operator
The spread operator is representend by three dots.
```
...
```
Technically, the spread operator lets you "split an iterable object into individual values". An iterable object is defined as an object that implements the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols">ES6 iteration protocol</a>.
In a practical sense it is useful to think of the spread operator as a way to split an array into individual values, like this:
```
"use strict";
var names = ["Bonnie","Dana","Lynda"];
var arr = [1,2,3,...names,4,5];
console.log(arr); // [ 1, 2, 3, 'Bonnie', 'Dana', 'Lynda', 4, 5 ]
```
You can use the spread operator to split an array to function arguments.
```
"use strict";
var names = ["Bonnie","Dana","Lynda"];
function complimentMakers(a,b,c){
return a + " is great!" +" "+ b + " is great! " + c + " is great!"
}
console.log(complimentMakers(...names)); // Bonnie is great! Dana is great! Lynda is great!
```
### Destructuring
Destructuring lets you extract data from arrays or objects and assign each value to distinct variables. To do this you use the *destructuring assignment expression*.
**Destructuring an array**
In ES5 if you want to assign individual elements of an array to distinct variables you would do this:
```
var things= ["item-1","item-2","item-3"];
var thingOne = arr[0];
var thingTwo = arr[1];
var thingThree arr[2];
```
In ES6 you do this:
```
"use strict";
let things = ["item-1","item-2","item-3"];
let a, b , c;
[a, b, c] = things; /* This is the destructuring assignment expression
The brackets to the left of the assignment operator are not an array.
*/
console.log( a ); // item-1
console.log( b ); // item-2
console.log( c ); // item-3
```
**Destructuring an object**
In ES5 if you want to assign individual object properties to variables you do this:
```
"use strict";
var user = {
name:"Bonnie",
age:60,
profession:"Antique dealer"
}
var name = user.name;
var age = user.age;
var profession = user.profession;
```
In ES6 you do this:
```
"use strict";
let user = {
name:"Bonnie",
age :60,
profession:"Antique dealer"
}
let name, age, profession; // You have to declare the variables first
({name, age, profession} = user); // Variable names must match property key names
console.log( name ); // Bonnie
console.log( age ); // 60
console.log( profession ); // Antique dealer
```
### Set
A set is list of values without duplicates. You create a new set using the Set constructor.
```
"use strict";
var items = new Set();
```
You add items to the set either on initialization by using array syntax, or by using the ```add``` method.
```
"use strict";
var items = new Set([1,2,3,4]); // add items to set
items.add(5) // add one more item
console.log(items); // Set { 1, 2, 3, 4, 5 }
```
If you add duplicate values they will be removed.
```
var items = new Set([1,2,3,4,5,5,5,5]); // add items to set
console.log(items); // Set { 1, 2, 3, 4, 5 }
```
To determine how many items a set contains you use the ```size``` method.
```
var items = new Set([1,2,3,4,5]); // add items to set
console.log(items.size); // 5
```
If you use objects as keys they are considered unique. Therefore, you can do this:
```
let items = new Set([{},{},{}]);
console.log(items.size); // 3
```
To check if a set contains a value you use the ```has``` method.
```
"use strict";
let user = {name:"Bob"};
let items = new Set([user,"12345"]);
console.log(items.has(user)); // true
console.log(items.has("12345")); // true
```
To remove an item from a set you use the ```delete``` method.
```
"use strict";
let user = {name:"Bob"};
let items = new Set([user]);
console.log(items.has(user)); // true
items.delete(user) // Remove user
console.log(items.has(user)); // false
```
To iterate over a Set you use a ```forEach``` method. This method takes a callback that takes three arguments. The first two arguments are the value and the third argument is the Set. The fact that the first two arguments return the same data might strike you as a bit odd. The reason for this behavior is that the ```forEach``` method that is used to iterate through *arrays* takes a callback that has three arguments. To keep the number of arguments consistant the Set implementation of ```forEach``` uses the first and second arguments for the same data.
```
let set = new Set(["item-1","item-2","item-3"]);
console.log(set);
set.forEach(function(val,index,set){
console.log(val); // item-1 item-2 item-3
console.log(index); // item-1 item-2 item-3
console.log(set); /* Set { 'item-1', 'item-2', 'item-3' }
Set { 'item-1', 'item-2', 'item-3' }
Set { 'item-1', 'item-2', 'item-3' }
*/
});
```
### Weak Set
A Weak Set is like a Set except it can only have objects as keys and is not iterable. Weak Sets do not have a forEach method.
### Map
When you create an object literal in JavaScript the keys are always strings. If you write your object literal keys using non-strings the interpreter will either throw an error or convert them to strings depending on the data type. Here is an example of using numbers as keys and the interpreter converting them to strings:
```
var item = {
1:"Hair dryer",
2:"Row machine",
3:"Lamp",
4:"Chair"
}
for (var prop in item) {
console.log( "typeof " + prop + " is " + typeof prop);
//typeof 1 is string typeof 2 is string typeof 3 is string typeof 4 is string
}
```
A map is like an object literal, but unlike object literals maps can have *any*data type as a key.
To create a map you initialize the Map constructor.
```
let map = new Map();
```
To add or replace keys/value pairs you use the ```set``` method. To retrieve values, you use the ```get``` method.
```
"use strict";
let map = new Map();
map.set(1, "Hair dryer");// 1 is a key and "Hair dryer" is a value
console.log(map.get(1)); // Hair dryer
map.set(1,"Book"); // Replace value of key
console.log(map.get(1)); // Book
```
Like object literals, you can assign functions to keys.
```
"use strict";
let map = new Map();
map.set(1, function(){
return "hello world"
});
console.log(map.get(1)()); // "hello world"
```
To initialize a map with multiple key/value pairs you must first place an array as an argument.
```
"use strict";
let map = new Map([]);
```
You now place arrays that have two values inside the initial array. The first value of each inner array is a key and the second is an assigned value.
```
"use strict";
let map = new Map([
[1,"Hair Dryer"],
[2,"Row machine"],
[3,"Lamp"],
[4,"Chair"]
]);
console.log(map.get(3)); // Lamp
```
You can determine the number of key/value pairs a map has by using the size property.
```
"use strict";
let map = new Map([
[1,"Hair Dryer"],
[2,"Row machine"],
[3,"Lamp"],
[4,"Chair"]
]);
console.log(map.size); // 4
```
To check if a map contains a key, you use the ```has``` method.
```
"use strict";
let user = new Map([
["name","Maureen"],
["age",40],
["profession","security guard"],
]);
user.has("age"); // true
```
To delete a key and its associated value you use the delete method.
```
"use strict";
let user = new Map([
["name","Maureen"],
["age",40],
["profession","security guard"],
]);
user.delete("name");
user.has("name"); // false
```
To remove all data from a map you use the ```clear``` method.
```
"use strict";
let user = new Map([
["name","Maureen"],
["age",40],
["profession","security guard"],
]);
user.clear();
console.log(user.size); // 0
```
Map keys must be unique. This means if you use the same key name more than once the previous key is overwritten.
```
"use strict";
let item1 = "test"; // same string
let item2 = "test"; // same string
let map = new Map();
map.set(item1,"stuff_1");
map.set(item2,"stuff_2");
console.log(map.size); // Size is 1 because item2 overwrites item1
```
If you use objects as keys, they are considered *unique* and you can do this:
```
"use strict";
let item1 = {};
let item2 = {};
let map = new Map();
map.set(item1,"stuff_1");
map.set(item2,"stuff_2");
console.log(map.size); // 2
```
The utillity of using objects as keys in a map is they allow you to associate additional data with the object without modifying it.
```
"use strict";
let map = new Map();
let maureen = {firstName: "Maureen", lastName: "Tanner"};
map.set(maureen, "This is my friend Maureen");
console.log(map.get(maureen)); // This is my friend Maureen
```
#### Iterating Maps
One problem with object literals that unlike arrays, there is no guarantee that data iteration will take place in any order.
ES6 maps do not have this problem. The data is accessible in order. To loop through a map, you use the ```forEach``` method. This method takes a callback that takes three arguments. The first argument is the value, the second is the key and the third is the entire map.
```
"use strict";
let map = new Map();
map.set("item-1","stuff-1");
map.set("item-2","stuff-2");
map.set("item-3","stuff-3");
map.forEach(function(value,key,map){
console.log(value);
console.log(key);
});
/*
item-1
stuff-2
item-2
stuff-3
item-3
*/
```
### Weak Map
The main difference between a Map and a Weak Map is Weak Map only uses objects as keys, their data cannot be iterated through and they are more memory efficient that Maps. You create a Weak Map using the Weak Map constructor.
```
"use strict";
let map = new WeakMap();
let maureen = {firstName: "Maureen", lastName: "Tanner"};
map.set(maureen, "This is my friend Maureen");
console.log(map.get(maureen)); // This is my friend Maureen
```
### Arrow Functions
Arrow functions are a syntactic replacement for anonymous functions.
An arrow function replaces the ```function()``` syntax with ```()=>```.
**An example without an arrow function**
```
"use strict";
var add = function(a,b){
return a + b;
}
console.log(add(1,2));
```
**An example with an arrow function**
```
"use strict";
var add = (a,b)=>{
return a + b;
}
console.log(add(1,2));
```
Arrow functions are intended as a replacement for non-method functions such as callbacks. Here is an example of a callback function with and without arrow function syntax.
**A callback example without an arrow function**
```
"use strict";
var arr = [5,4,3,2,1];
arr.forEach(function(val){ // callback
console.log(val);
// Do more stuff
});
```
**A callback example with an arrow function**
```
"use strict";
var arr = [5,4,3,2,1];
arr.forEach((val)=>{ // callback rewritten as an arrow function
console.log(val);
// Do more stuff
});
```
#### Single Expression Arrow Functions
If your arrow function contains a *single expression* like the following example, it can be rewritten without curley braces or an explicit return statement.
```
"use strict";
var add = (a,b)=>{
return a + b;
}
console.log(add(1,2));
```
```
var add = (a,b)=> a + b; /* When written like this, the returned value is implicit */
console.log(add(1,2)); // 3
```
#### The Value of "this" in an Arrow Function
If you use an arrow function inside of another function the inner functions ```this``` keyword points to the same object as the harboring function. This is not the case when using non-arrow functions. To clarify, here is an example:
**Non-arrow function example**
```
var user = {
name:"Bob",
thisMethodPointsToUserObject:function(){
var innerFunction = function(){
console.log(this); // If using "strict mode" "this" is undefined.
} // If not using "strict mode" "this" points to
// the window object
innerFunction()
}
}
user.thisMethodPointsToUserObject();
```
**Arrow function example**
```
var user = {
name:"Bob",
thisMethodPointsToObject:function(){
var arrowFunc = ()=>{
console.log(this); // The "this" of an arrow function points to the
} // same object as its containing method
arrowFunc()
}
}
user.thisMethodPointsToObject();
```
The only other difference between conventional functions and arrow functions is that the "new" keyword cannot be applied to arrow functions.
### The Class Keyword
In most object orientated programming languages a construct called a "class" is used to create objects. A class acts as a template or blueprint for an object. Technically JavaScript does not have classes but you can replicate their behavior by using ```Object.create()```, factory functions or constructors. In ES6 yet another tool is available to replicate class behavior - a class keyword. Technically, JavaScript still behaves as a class-less language. The class keyword simply wraps JavaScript’s preexisting constructor functionality in a new syntax. The purpose of this is to make JavaScript *look* and "feel" like other object orientated languages. You do not need to use the class keyword to write good programs, but you do need to understand how it works.
You are now going to build a class to create "user" objects. Each user object has name and age properties. It also has a method named ```userData``` that retrieves the data assigned to the property values and returns a string that says: "```name``` is ```age``` years old".
The first step is to name the class and create the body. Class names are capitalized.
```
class User {
}
```
To add properties, you create a function named ```constructor``` and add your properties like this:
```
class Student{
constructor(name, age){
this.name = name;
this.age = age;
}
}
```
To add methods to your class you place them outside of the constructor function like this:
```
class Student{
constructor(name, age){
this.name = name;
this.age = age;
}
//_______________________BEGIN method
userData(){
return this.name +" is "+ this.age + " old"
}
//_______________________END method
}
```
To return a new object you invoke the class using the ```new``` keyword.
```
var lynda = new Student("Lynda",30);
console.log(lynda.name); // Lynda
console.log(lynda.age); // 30
console.log(lynda.getData()); // Lynda is 30 old
```
#### Extending Classes
In a previous module you learned that you can extend an object to include new properties and methods using Object.create(). When using ES6 classes you can extend a *class*. This allows you to contribute new methods and properties to a class without changing the original code.
The following example contains a class that represents a player in a video game. This class has two properties ```name``` and ```meters```; and a method ```moveForward()```. When the ```moveForward``` method is invoked the ```meters``` property is incremented with the total number of "meters" the player has traveled.
```
"use strict";
class Player {
constructor(name){
this.name = name;
this.meters = 0;
}
moveForward(numberOfMeters){
return this.meters += numberOfMeters
}
}
let bob = new Player("Bob");
console.log(bob.moveForward(1)); // total number of meters is 1
console.log(bob.moveForward(9)); // total number of meters is 10
```
To contribute to this class without changing the original code you can extend it by creating a new class that inherits from the base class. The following example creates a class named ```Alien``` that extends the class named ```Player```.The ```Alien``` class contains a new property named ```power``` and a method named ```flyUp```.
```
"use strict";
class Player {
constructor(name){
this.name = name;
this.meters = 0;
}
moveForward(numberOfMeters){
return this.meters += numberOfMeters
}
}
let bob = new Player("Bob");
console.log(bob.moveForward(1)); // total number of meters is 1
console.log(bob.moveForward(9)); // total number of meters is 10
class Alien extends Player{
constructor(name,power){
super(name);
this.power = power;
}
flyUp(){
return "You are now flying"
}
}
let zod = new Alien("Zod","Zapper");
console.log(zod.moveForward(100)); // total number of meters is 100
console.log(zod.power); // Zapper
console.log(zod.flyUp()); // you are now flying
console.log(zod.moveForward(100)); // total number of meters is 200
```
#### Static Class Methods
To create a method that is invoked directly on a class and *not from an object created from a class* you use the ```static``` keyword.
```
"use strict";
class Player {
constructor(name){
this.name = name;
this.meters = 0;
}
moveForward(numberOfMeters){
return this.meters += numberOfMeters
}
static sayHello(){ // Static method
return "Hello"
}
}
console.log(Player.sayHello()); // Is invoked directly on Player. Returns Hello
let bob = new Player("Bob");
bob.sayHello() // Error. Is not available on returned objects
```
### Promises
If you catch yourself writing code that requires a lot of asynchronous communication with the web browser (or other JavaScript environment such as Node.js ) and successive callbacks become difficult to reason about, then promises can help you to organize your code. The goal of promises is to remedy the "nested callback" problem.
This is what a nested collection of callbacks looks like:
```
getData(function(data) {
doSomething1(function(data1) {
doSomething2(function(data2) {
doSomething3(function(data3) {
doSomething4(function(data4) {
});
});
});
});
});
```
The following example is a "promise" version of the previous code.
```
getData()
.then(doSomething1)
.then(doSomething2)
.then(doSomething3)
.then(doSomething4)
```
Although you may consider the end result of using promises to being more syntactically pleasant, this does not mean writing programs that use promises is less work or that your code will be free of callbacks. Writing effective JavaScript requires the use of callbacks and promises are only used to solve the nesting problem.
We will explain promises in more technical detail momentarily, for now the goal is to teach you how to *use* promises as an alternative to nested callbacks.
#### Creating Your First Promise
A promise is created with the Promise constructor, therefore to create a promise requires the use of the ```new``` keyword. The promise takes a callback that takes two arguments: resolve and reject.
```
var myPromise = new Promise(function(resolve,reject){
})
```
The following code is the same as the preceding example and written using an arrow function:
```
var myPromise = new Promise((resolve,reject)=>{
})
```
When coding promises you want to look at them as a *wrapper* for any async function that you use to communicate with the web browser. Wrapping an async function in a promise is called "promisfying" it.
You will now write code to "promisfy" a ```setTimeout``` function.
**Step 1.** Determine the function you want to "promisfy".
```
setTimeout(function(){ // We will promisfy the setTimeout function
},1000)
```
**Step 2.** Create a function
```
let timer = function () { // Step 2. Create a function
};
```
**Step 3.** Return a promise from that function
```
let timer = function () {
return new Promise((resolve, reject) => { // Step 3. Return a promise
});
};
```
**Step 4.** Wrap the code you want to "promisfy" in the promise you created in step 3.
To "promisfy" code you wrap it in the returned promise (in this case you are "promisfying" a ```setTimeout``` function but this could be an ```XMLHttpRequest``` or any other number of built-in async browser functions).
```
let timer = function (value) {
return new Promise((resolve, reject) => {
//___________________Step 5. wrapped async function
setTimeout(() => {
console.log(value);
}, value);
//_____________________END wrapped async function
});
};
```
**Step 6.** Set the resolve function.
The ```resolve()``` of a *promise* is like the ```return``` of a *function*. The ```resolve``` argument is a function that is invoked when the promise succesfully completes. The ```resolve()``` function takes an argument that is the value returned from the promise. In this case, the value placed as the argument of ```resolve()``` is the millisecond value passed to the setTimeout.
```
let timer = function (value) { //__passed in argument named value
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(value);
resolve(value); //____This is the returned value of a promise
}, value); //____value argument is millisecond value of setTimeout
});
};
```
You then invoke your promise.
```
timer(1000);
```
Every promise has a ```then``` method and takes a callback that executes code after the previous async operation completes.
```
timer(1000) // after timer completes
.then(function(){
return timer(1000); // run this code
});
```
Promisfied functions can be chained using the then method.
```
timer(1000)
.then(function(){
return timer(1000)
})
.then(function(){
return timer(1000)
})
.then(function(){
return timer(1000)
})
```
The preceding code can be refactored with arrow functions to make it less verbose.
```
timer(1000)
.then(()=>timer(1000))
.then(()=>timer(1000))
.then(()=>timer(1000))
```
If you were to write the previous code without a promise it would look something like this:
```
setTimeout(() => {
// do something
setTimeout(() => {
// do something
setTimeout(() => {
// do something
}, 1000);
}, 1000);
}, 1000);
```
#### Promises in Detail
A promise is always in one of three states: pending, resolved or rejected. When a promise is *pending* it means that it is in the process of making an asynchronous request. If a promise has *resolved* it means it has completed a successful request. This gives you access to the ```then``` method. If a promise is rejected it means the request failed, and this gives you access to a method named ```catch``` (demonstrated below).
#### Resolve
To resolve a promise the resolve function must be invoked. A resolved function takes an argument that is the data returned from the promise. This data is accessible as an argument of the ```then``` method. Here is an example:
```
"use strict";
function doStuff(){
return new Promise((resolve,reject)=>{
// do some stuff
resolve("some data") // To resolve the promise, this function must be invoked
})
}
doStuff().then((data)=>{ // returned resolve function data is passed here
console.log(data); // some data
});
```
#### Reject
The reject method of a promise is used to return an error when the "promisfied" code fails. If a completed promise does not *resolve* then it *rejects*. The rejection of a promise gives you access to a method named ```catch``` and lets you run code based on the promise rejection via a callback. Here is an example:
```
"use strict";
function doStuff(){
return new Promise((resolve,reject)=>{
// no resolve method
reject("rejected!")
});
}
doStuff().catch((error)=>{
console.log(error); // rejected!
});
```
The following example code combines the resolve and reject methods together into one example. Here, a function returns a promise that wraps a ```setTimeout``` function. The ```setTimeout``` will only run if the ```time``` argument is less than 3000 milliseconds (3 seconds). If the time value is greater than 3000 milliseconds then the reject method will be invoked and the catch method will give you access to the returned error.
```
function timer(time,message){
return new Promise((resolve,reject)=>{
if(time <= 3000){ // If time is less than 3 seconds
setTimeout(()=>{
resolve(message) // Run code
},time)
}else{ // Else return error message
reject(new Error("Time needs to be less than 3 seconds"))
}
})
}
var value = 5000;
timer(value,"Timer up").then((message)=>{
console.log(message);
}).catch((error)=>{
console.log(error);
});
```
### Symbols
ES6 adds a new data type to JavaScript - *Symbol*. A symbol only evaluates to true when compared to itself. To create a symbol you use the function ```Symbol()```. The symbol function takes a single argument and is a *descriptive string*. The syntax looks like this:
```
"use strict";
let data = Symbol("some data");
console.log(data); // Symbol(some data)
console.log(data === data); // true
console.log( Symbol("some data") === Symbol("some data") ); // false
console.log(data === "some data"); // false
```
#### What Are Symbols Used For ?
> They were added to the language in order to solve the problem of extending the functionality of Object while maintaining backwards-compatibility with code written in earlier versions of JavaScript. - <a href="https://www.sitepen.com/blog/2016/04/13/es6-symbols-drumroll-please/">-source</a>
<!-- -->
>[Symbols] allow properties to be added to existing objects with no possibility of interference with the existing object properties, with no unintended visibility, and with no other uncoordinated additions by other code. - <a href="https://msdn.microsoft.com/en-us/library/dn919632(v=vs.94).aspx">-msdn</a>
<!-- -->
> Their only use is to avoid name clashes between properties <a href="http://stackoverflow.com/questions/21724326/why-bring-symbols-to-javascript"> - source</a>
In other words, you use symbols to create object property keys and their purpose is to protect object properties from being overwritten or "clashing".
#### Using Symbols to Create Property Keys
```
"use strict";
var user = {
id:"1111111",
name:"Bob",
age:30,
}
var id = Symbol("symbol one"); // Create a Symbol
user[id] = "id 2222222"; // Make it a key and assign it some data
var id = Symbol("symbol two"); // Create a new Symbol...What! It's the same variable name!
user[id] = "id 3333333"; // Make it a key and assign it some data
console.log(user);
/* The object contains both symbols and original id property. No overwrites!
{
id: "1111111",
name: "Bob",
age: 30,
Symbol(symbol one): "id 2222222",
Symbol(symbol two): "id 3333333"
}
*/
```
#### Accessing Unique Symbols
If an object has multiple property keys of the same name using a symbol then the way you can access it is using a method of ```Symbol``` named ```for```.
The for method allows you to place a Symbol in a global system registery.You can then retrive them using the same method.
```
var id = Symbol.for("my id");
var user = {
name:"Bob",
age:30,
[id]:"my id 12345"
}
var id = Symbol.for("my different id");
user[id] = "my different id 9876"
console.log(user[Symbol.for("my id")]); // my id 12345
console.log(user[Symbol.for("my different id")]); // my different id 9874
```
When you iterate through an object symbols are not accessible.
```
"use strict";
let id = Symbol("id");
var user = {
name:"Bob",
age:30,
[id]:"8726348726432"
}
for(var prop in user){
console.log(user[prop]); // Bob 30 (no id)
}
```
The most common use for Symbols won't be making your own but rather using the built in "well-known symbols" like ```Symbol.iterator``` (introduced in in the *iterables and iterators* section below).
Like classes you do not need to use Symbols to write good programs but because they are a new data type, it is important that you have a working knowledge of them. For more information, we suggest you read <a href="http://stackoverflow.com/questions/21724326/why-bring-symbols-to-javascript">this StackOverflow thread.</a>
### Iterators and Iterables
In the ES5 version of JavaScript you typically loop over a collection of data using a for-loop. ES6 has adopted an alternative approach that uses objects called iterators that programmatically return the next value in a collection. Unlike for-loops, iterators do not require the creation of an initialization variable to track collections. *The purose of adopting iterators is to make iterating through data easier and more efficient.* One of the reasons for adding Symbols to JavaScript is to implement this feature.
If a data source (such as array) has the property ```Symbol.iterator``` on its prototype it means it is designed according to a set of instructions called the *iterable protocol* and is referred to an an *iterable*.
> The iterable protocol allows JavaScript objects to define or customize their iteration behavior - <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols">MDN</a>
You can inspect the prototype of a data source for the ```Symbol.iterator``` property and see if it is iterable.
<img src="images/array_symbol_console_log.PNG" alt="array console.log of symbol iterator property" width = "750">
Some collections are not iterable, such as object literals and the property ```Symbol.iterator``` is non-existant on their prototype.
<img src="images/object_console_log.PNG" alt="object console.log" width = "750">
The for-of loop only works on iterable collections. Object literals are not iterable.
```
var numbObj = {
zero:0,
one:1,
two:2
};
for (var prop of numbObj){
console.log(prop); // ERROR! Uncaught TypeError: numbObj[Symbol.iterator] is not a function
}
```
#### List of Iterables
The following data collections have the ```Symbol.iterator``` property on their prototype. The for-of loop is applicaple to all of these collections.
**Arrays**
```
"use strict";
var numbs = [0,1,2];
for (var prop of numbs){
console.log(prop); // 0 1 2 Works! This is because arrays are iterables!
}
```
**Strings**
```
"use strict";
for (let val of 'hello world') {
console.log(val); // h e l l o w o r l d
}
```
**Maps**
```
"use strict";
let map = new Map();
map.set('item1', 1)
map.set('item2', 2);
for (let item of map) {
console.log(item); // [ 'item1', 1 ] [ 'item2', 2 ]
}
```
**Sets**
```
"use strict";
let set = new Set();
set.add('item-1');
set.add('item-2');
for (let item of set) {
console.log(item); // item-1 item-2
}
```
**The arguments object**
The arguments object of a function is iterable.
```
"use strict";
function doStuff(a,b,c){
for(var item of arguments){
console.log(item);
}
}
doStuff(1,2,3) // console logs: 1 2 3
```
**DOM node collections (in progress)**
DOM node collections are being incorporated into the iteration protocol. In the current version of Chrome the following code works:
**HTML**
```
<body>
<div>item-1</div>
<div>item-2</div>
<div>item-3</div>
<script src="js/app.js"></script>
</body>
```
**JavaScript**
```
for(let node of document.querySelectorAll("div")){
console.log(node); /* <div>item-1</div>
<div>item-2</div>
<div>item-3</div>
*/
}
```
The ```Symbol.iterator``` property is a function. If you invoke the function it returns an *iterator* object.
You can run a ```next``` method on an iterator. This example demonstrates using an array.
```
"use strict";
let arr = [3,2,1];
let iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 3, done: false }
iterator.next(); // { value: 2, done: false }
iterator.next(); // { value: 1, done: false }
```
The following example is the same as the previous one but it uses a DOM selector instead.
**HTML**
```
<body>
<div>item-1</div>
<div>item-2</div>
<div>item-3</div>
<script src="js/app.js"></script>
</body>
```
**JavaScript**
```
"use strict";
let node = document.querySelectorAll("div")[Symbol.iterator]();
console.log(node.next()); //Object {value: div, done: false}
console.log(node.next()); //Object {value: div, done: false}
console.log(node.next()); //Object {value: div, done: false}
```
The done property returns true when the iteration is complete.
```
"use strict";
let arr = [3,2,1];
let iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 3, done: false }
iterator.next(); // { value: 2, done: false }
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: undefined, done: true } done is true
```
Although the previous listed collections are considered iterable, there are many other JavaScript language constructs that make use of the iteration protocol including *generators*.
### Generators
Generators are functions that have the abillity to pause their execution.
You create a generator by placing an asterisk between the function keyword and the name of the function.
```
function *doStuff(){
}
```
```
"use strict";
function *doStuff(){
yield 100;
yield 200;
yield 300;
}
let iterable = doStuff();
iterable.next(); // { value: 100, done: false }
iterable.next(); // { value: 200, done: false }
iterable.next(); // { value: 300, done: false }
iterable.next(); // { value: undefined, done: true }
```
```
// NOTE: Generator functions have Symbol.iterator on their prototype
"use strict";
function *doStuff(){
}
console.log(doStuff.prototype);
```
Generators have a return statement
// example
Generators can yield other generators
// example
### Summary
### Exercises
1. "Promisfy" an ```XMLHttpRequest``` that makes a ```GET``` request to a URL. The URL is passed as an argument from the containing function. Use ```then``` to chain multiple calls to various web api's and log the returned data successively.
The end result will look similar to this:
```
AJAXRequest('https://itunes.apple.com/hk/rss/topalbums/limit=3/json')
.then(() => AJAXRequest ('https://api.github.com/users/bob'))
.then(() => AJAXRequest ('https://itunes.apple.com/hk/rss/topalbums/limit=10/json'))
```
**Bonus**
Wrap a prompt method is a promise and use it to prompt for a github user name. Connect the promisfied prompt method to you AJAXRequest function and return the data for the Github user name.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment