ECMAScript Workshop – Variables & scope – Promises



ECMAScript Workshop – Variables & scope – Promises

2 0


ecmascript-workshop-slides

Slides used under the ECMAScript workshop

On Github BouvetNord / ecmascript-workshop-slides

ECMAScript Workshop

Transpilation

Browser support for ECMAScript 6 features is not extensive yet and varies alot.

This is why we need to convert it to the previous version of JavaScript, called ECMAScript 5, which runs fine in any modern browser.

This conversion is often referred to as transpilation, and Babel is a popular solution to do that

So, what excatly can i use?

Some of the features can be used almost for "free", such as modules, arrow functions, and classes withouth to much overhead (syntatical sugar).

Additions to Array, String and Math objects and prototypes (such as Array.from()) require so-called polyfills (provide missing code).

For a full overview of features that are supported by both transpilers and browers, see ECMAScript 6 compability table

Today

  • Modules
  • Variables & scope
  • Template strings
  • Destructuring
  • Classes
  • Generators
  • Promises
  • Arrow functions
  • Maps/Sets
  • Iterator & iterable

Variables & scope

Spoiler alert!

Variables are so much more fun in ES6 - not! But they are a lot nicer(les:more predictible) to hang out with than the old var chap of ES5!

let & const

- ES6 brings two new ways to declare variables: let and const - These basically replace, the ES5 way of declaring variables, using var - However, var is still around

let

Here is an example where the let declared variable tmp, only exists within the block starting in line A:

							function switch(x, y) {
if (x > y) { // (A)
	let tmp = x;
	x = y;
	y = tmp;
}
console.log(tmp===x); // ReferenceError: tmp is not defined
return [x, y];
}
						

const

const is a read-only, initialize at once constant type. Here are a few examples of correct usage and wrong usage leading to SyntaxError and TypeError:

							const foo;
    // SyntaxError: missing = in const declaration

const bar = 123;
bar = 456;
    // TypeError: `bar` is read-only
						

Promises

Promises - que?

- Promises are a pattern, helping out with one particular kind of asynchronous programming: "a function (or method) that returns its result asynchronously." - To implement such a function, you return a Promise, an object that is a placeholder for the result. - The caller of the function registers callbacks with the Promise to be notified once the result has been computed. - The function sends the result via the Promise. The de-facto standard for JavaScript Promises is called Promises/A+ [1]. The ECMAScript 6 Promise API follows that standard.

Promises - when to use

A gentle example, to give you a feeling of what working with Promises will be like. With Node.js-style callbacks, reading a file asynchronously looks like this:

							fs.readFile('config.json',
    function (error, text) {
        if (error) {
            console.error('Error while reading config file');
        } else {
            try {
                let obj = JSON.parse(text);
                console.log(JSON.stringify(obj, null, 4));
            } catch (e) {
                console.error('Invalid JSON in file');
            }
        }
    });
						

Promises applied

With Promises, the same functionality is implemented like this:

							readFilePromisified('config.json')
.then(function (text) { // (A)
    let obj = JSON.parse(text);
    console.log(JSON.stringify(obj, null, 4));
})
.catch(function (reason) { // (B)
    // File read error or JSON SyntaxError
    console.error('An error occurred', reason);
});
						

There are still callbacks, but they are provided via methods that are invoked on the result (then() and catch()). The error callback in line B is convenient in two ways: First, it’s a single style of handling errors (versus if (error) and try-catch in the previous example). Second, you can handle the errors of both readFilePromisified() and the callback in line A from a single location.

Modules

Benefits

No more global namespace pollution.

Defaults

There is exactly one module per file and one file per module.

By default anything you declare in a file is not available outside that file (awesome).

Named exports

A module can export multiple things by prefixing its declarations with the keyword export:

export var myVar1 = ...;
export let myVar2 = ...;
export const MY_CONST = ...;
export function myFunc() {}
export class MyClass {}

These exports are distinguished by their names and are called named exports.

import { myFunc, myVar1 } from 'lib';
myFunc();

Named exports

You can also list everything you want to export at the end of the module (which is once again similar in style to the revealing module pattern):

const MY_CONST = ...;
function myFunc() {}
export { MY_CONST, myFunc };

import the whole module

You can also import the complete module using a wildcard:

import * as lib from 'lib';
lib.myFunc();

Single default export

If you want to export a single value from the module then you can use default export.

For example, a class:

export default class {}

And import like this (omit the curly braces):

import MyClass from 'MyClass';
var instance = new MyClass();

Mixture

Module can have both named exports and a default export.

In such cases, to import a module's default export, you have to omit the curly braces in the import statement:

import myDefault, { foo, bar } from 'lib';

Best practise?

Only use export default, and to do that at the end of your module files. Like this:

var api = {
  foo: 'bar',
  baz: 'ponyfoo'
}
export default api

Easy then to import the whole thing like this:

import api from 'api';
api.foo; // bar

Code task 1 - export two named functions

In `src/00-modules/named.js´ create and export two named functions:

  • sum(x,y) - A function that add two numbers
  • multiply(x,y) - A function that multiply two numbers

Code task 2 - export two functions by default

In `src/00-modules/default.js` import the two functions created in the previous task, then export them again, but as a single default export.

Export Syntax

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export

Import Syntax

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

Classes

Benefits

Simpler and clearer syntax to create objects and to deal with inheritance

Syntatical sugar

Classes aren't really 'classes', instead they are functions which are in turn objects

It’s important to understand that while JavaScript is an object-oriented language, it is prototype-based and does not implement a traditional class system.

JavaScript Objects

Almost everything in JavaScript are objects (including functions and arrays). Except for primitives:

  • numbers
  • boolean
  • strings
  • undefined
  • null

JavaScript Objects

An object is a collection of properties, and a property is an association between a name (or key) and a value. A property's value can be a function, in which case the property is known as a method.

Creating A JavaScript Object

// The easiest way to create an object is like this:
let myObject = { 'key1': "Awesome", 'key2': "Object" }

// Or:
var myObject = Object.create(new Object());
myObject.key1 = "Awesome";
myObject.key2 = "Object";

// Or:
function MyObject(value1, value2) {
  this.key1 = value1;
  this.key2 = value2;
}
var myObject = new MyObject("Awesome", "Object");

Which of these alternatives are suited to create multiple instances of same type?

Using A JavaScript Object

// Get a property
var value = myObject.key1;
var value = myObject["key1"];

// Set a property
myObject.property = "Value";
myObject["property with space in it"] = "Value";

// Call a method
myObject.toString();

// To find out if a property exists on an object
'key1' in myObject; // true

Classes in ECMAScript 5

function MyClass (id) {
     var privateField = "My secret";
     // Instance property
     this.id = id;
}

// Instance method
MyClass.prototype.myMethod = function () {};

var instance1 = new MyClass(1);
var instance2 = new MyClass(2);
instance1.myMethod();
instance1.id; // 1
instance2.myMethod();
instance2.id; // 2
instance1 instanceof MyClass; // true

Prototype methods are shared across all instances of the class. One method, many instances!

Why is MyClass a function, shouldn’t it be a class?

MyClass is a constructor function, and combined with the new keyword, work together to create new objects in much in the same way classes do in other OO languages.

When you create an object using the new keyword, it creates a new object, passes it in as this to the constructor function.

Inherited properties and own properties

The own properties are properties that were defined on the instance, while the inherited properties were inherited from the Function’s Prototype object.

instance1.hasOwnProperty("id"); // true
instance1.hasOwnProperty("myMethod"); // false

Functions have a prototype

When you define a function within JavaScript, it comes with a few pre-defined properties and one of these is the illusive prototype.

(1) Initially an empty object. (2) You can add methods and properties on a function’s prototype property to make those methods and properties available to instances of that function. All class instances, past and future, will be affected.

Prototypal inheritance

Objects inherit from other objects.

How does prototype inheritance work?

Every object in JavaScript has a special related object called the prototype.

vehicle.__proto__ = machine
// machine is the prototype of vehicle

car.__proto__ = vehicle
// vehicle is the prototype of car

This is a prototype chain: car -> vehicle -> machine

When looking up a property, Javascript will try to find the property in the object itself. If it does not find it then it tries in it's prototype, and so on.

Functions vs objects prototype

A function’s prototype is used as the object to be assigned as the prototype for new objects’ created using this function as a constructor function.

instance.__proto__ = Function.prototype

An object’s prototype is the object from which it is inheriting properties.

Subclasses in ECMAScript 5

function MySubClass(id) {
  MyClass.call(this, id);
}

// Inherit from the parent class
MySubClass.prototype = Object.create(MyClass.prototype);
MySubClass.prototype.constructor = MySubClass;

// Child class method
MySubClass.prototype.myMethod = function() {
  MyClass.prototype.method.call(this);
}

Easy to read?

Classes in ECMAScript 6

class MyClass {
    constructor (id) {
        this.id = id
    }
    myMethod () {}
    static myStaticMethod() {}
}
let instance = new MyClass(1);
instance instanceof MyClass; // true
typeof MyClass // 'function' (old-school constructor function)
MyClass.myStaticMethod();
darth.hasOwnProperty("myMethod")
MyClass.prototype.myMethod(); // prototype-based

The class keyword is syntactical sugar, JavaScript remaining prototype-based!

This is the prototype chain: MyClass -> Object

Subclasses in ECMAScript 6

class MySubClass extends MyClass {
    constructor (id) {
        super(id)
    }
}

This is the prototype chain: MySubClass -> MyClass -> Object

Code task 3 - Create a class

Create a class equal to the ECMAScript 5 code:

function Point(x,y) {
    this.x = x;
    this.y = y;
}
// Static method
Point.distance = function (a, b) {
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return Math.sqrt(dx*dx + dy*dy);
};
Point.prototype.toString = function () {
    return this.x + ' ' + this.y;
};
module.exports = Point;

Code task 4 - Class inheritance

Extend the Point class from previous task so that it behaves equal to the ECMAScript 5 code below:

// Child class constructor
var ColorPoint = function (x, y, color) {
    Point.call(this, x, y);
    this.color = color || 'red';
};

// Inherit from the parent class
ColorPoint.prototype = Object.create(Point.prototype);
ColorPoint.prototype.constructor = ColorPoint;

// Child class method
ColorPoint.prototype.getColor = function () {
    return this.color;
};
module.exports = ColorPoint;

References

Iterator & iterable

for-of-loop

ES6 has a new loop — for-of. It works with iterables. Let’s look at his signature:

for (let item of ITERABLE) {
  CODE BLOCK
}

It’s similar to for-in loop, which can be used to iterate through object properties.

Iterable

Iterable is an object which has [Symbol.iterator]() method inside.

The [Symbol.iterator] method must return an iterator object, which is actually responsible for the iteration logic.

Iterator

An iterator is an object with a next method that returns { done, value } tuples.

Arrays are iterable by default

We finally can use for-of for looping over the elements:

const arr = [1, 2, 3, 4, 5];
for (let item of arr) {
  console.log(item); // 1
                     // 2
                     // 3
                     // 4
                     // 5
}

Symbol

Symbol is in turn an unique and immutable data type which can be used as an identifier for object properties — no equivalent in ES5.

Symbol

// Symbol
let s1 = Symbol('abc');
let s2 = Symbol('abc');
console.log(s1 !== s2); // true
console.log(typeof s1); // 'symbol'
let obj = {};
obj[s1] = 'abc';
console.log(obj); // Object { Symbol(abc): 'abc' }

You can use symbols to create unique identifiers for object properties.

Symbols are not visible in `for...in` iterations and object.keys()

Symbol.iterator

The Symbol.iterator well-known symbol specifies the default iterator for an object.

Encapsulating collections

class Classroom {
    constructor() {
        this.students = ["Tim", "Joy", "Sue"];
    }
}

Option 1: Return a reference to the array, in which case the caller might change the array by adding or removing items.

Option 2: Another option is to make a copy of the array. Then, the original student array remains safe. However, copy operations can be expensive.

Option 3: Make the classroom iterable by adding a [Symbol.iterator]() method.

function PointCloud(list) {
    this.collection = list;
}
PointCloud.prototype[Symbol.iterator] = function() {
    var index = 0;
    var array = this.collection;
    return {
        next: () => {
            var result = { value: undefined, done: true };
            if (index < array.length) {
                result.value = array[index++];
                result.done = false;
            }
            return result;
        }
    };
};
module.exports = PointCloud;

References

  • https://medium.com/ecmascript-2015/es6-iterators-4afec026f70a#.6wfbn4xnc
  • http://jsrocks.org/2015/09/javascript-iterables-and-iterators/
  • http://odetocode.com/blogs/scott/archive/2015/02/09/creating-iterables-in-ecmascript-6.aspx

Arrow functions

() => {}

Benefits

  • function(){...} can now be written as () => {...}
    • Shorter syntax than using traditional anonymous functions
  • Lexically binds the this value
    • Which is where arrow functions shine

A quick overview of the syntax:

							// Basic syntax:
(param1, param2, paramN) => { statements }
(param1, param2, paramN) => expression
// equivalent to:  => { return expression; }

// Parentheses are optional when there's only one argument:
(singleParam) => { statements }
singleParam => { statements }

// A function with no arguments requires parentheses:
() => { statements }
						

Another example:

							function(x) { return x+1;}

x => x+1;
						

Here a2 is using traditional anonymous functions while a3 is using the arrow function syntax.

							var a = ["Hydrogen", "Helium", "Lithium", "Beryl­lium"];

var a2 = a.map(function(s){ return s.length });
var a3 = a.map( s => s.length );

//a2 = [8, 6, 7, 10]
//a3 = [8, 6, 7, 10]
						

Lexically binding this

  • Javascript uses lexical scope
    • Like Python, Java ..
  • Block scope: let (pre ECMAScript 2015)
							function foo(a) {
	var b = a * 2;

	function bar(c) {
		console.log( a, b, c );
	}
	bar(b * 3);
}

foo( 2 ); // 2 4 12
						
  • Every new function defines a new this.
  • Object methods gets the context of the object.
    									function person(firstName, lastName, age, eyeColor) {
    	this.firstName = firstName;
    	this.lastName = lastName;
    	this.changeName = function (name) {
        	this.lastName = name;
        };
    }
    								
  • When calling a function (not an object method) the this of the object is not reachable.
								function Person() {
	// The Person() constructor defines `this` as an instance of itself.
	this.age = 0;

	setInterval(function growUp() {
		// In nonstrict mode, the growUp() function defines `this`
		// as the global object, which is different from the `this`
		// defined by the Person() constructor.
		this.age++;
	}, 1000);
}
var p = new Person();
							

That can be solved like this:

							function Person() {
	var self = this;
	// Some choose `that` instead of `self`.
	// Choose one and be consistent.
 	self.age = 0;

	setInterval(function growUp() {
		// The callback refers to the `self` variable of which
		// the value is the expected object.
		self.age++;
	}, 1000);
}
var p = new Person()
						

With arrow functions and lexical binding of this, the problem can be solved like this:

							function Person(){
	this.age = 0;

	setInterval(() => {
		this.age++; // |this| properly refers to the person object
	}, 1000);
}
						

References

Destructuring

let {a,b} = obj;

Array destructing

							let foo = ["one", "two", "three"];

// without destructuring
let one   = foo[0];
let two   = foo[1];
let three = foo[2];

// with destructuring
let [one, two, three] = foo;
						

Object destructuring

							let o = {p: 42, q: true};
let {p, q} = o;
						

It is also possible to give the new variable a new name by using:

							let {p:age, q}
						

Object destructuring

							var metadata = {
	title: "Scratchpad",
	translations: [
		{
			locale: "de",
			localization_tags: [ ],
			last_edit: "2014-04-14T08:43:37",
			url: "/de/docs/Tools/Scratchpad",
			title: "JavaScript-Umgebung"
		}
	],
	url: "/en-US/docs/Tools/Scratchpad"
};

let { title: englishTitle, translations: [{ title: localeTitle }] } = metadata;
						

What else?

It is also possible to:
  • Swap variables:
    									[a, b] = [b, a];
    								
  • Ignore returned values:
    									var [a, , b] = f();
    								
  • Use destructuring in loops
    									for (var {name: n, family: { father: f } } of people) {...}
    								

References

Generators

What?

  • function*, yield, yield*
  • Looks like normal function + keywords
  • Is a type of iterator
  • Keeps state between invocations
  • Return list vs. yield item

Gang of Three

  • Iterators
  • Observers
  • Co-routines

Iterators

  • yield data;
  • Iterable (for let v of vals)
  • No initialization
  • var help = function* helpiterable() {
        yield 100;
    }
    
    function* myiterable() {
        while(true) {   
            yield 1;
            yield 2;
            for(var i=0;i<10;i++) {
                yield i*2;
            }
            yield* [1,2,3];
            yield* help();
            yield; // yield undefined;
        }
        return 0;
        // if return is not added, then it is implicit as return undefined;
    }

Observers

  • var data = yield;
  • var data = (yield)++;
  • Requires initialization (to start observer)
  • Pauses until input is received
  • Can wrap to avoid explicit initialization
  • function* myobserver() {
        console.log(yield);
    }
    
    var miobserver = myobserver();
    miobserver.next(); // initialize
    miobserver.next("test"); // writes "test" to console

Co-routines

  • Produces AND consumes data
  • Complex
  • Avoid unless absolutely needed
  • function* mycoroutine() {
        yield 1; // A
        console.log(yield); // B
        console.log(yield); // C
    }
    
    var micoroutine = mycoroutine();
    var i = micoroutine.next(); // initialize // A, and pauses at A
    micoroutine.next(); // goes to B, and pauses, waits for data
    micoroutine.next("test"); // writes "test" to console and continues until next yield, pauses // B->C
    micoroutine.next("test2"); // writes "test" to console goes to invisible return // B->C
    console.log(micoroutine.next()); // returns "undefined" from return undefined; exits generator

Why? Usage!

  • As iterator
  • Lazy evaluation
  • Less memory consumption
  • Less latency
  • Avoid callback-hell
  • More fluent (synchronized)
  • Chain generators UNIX way
  • Infinite collection

Finally

  • yield pauses/suspends the generator
  • next() or next(data) to resume
  • Generator continues from exactly where it paused
  • yield resumes execution until explicit or implicit yield, return, throw
  • Generator is initially suspended at start of its body
  • function* producer() {
            console.log('1');
            yield; // (A)
            console.log('2'); // (B)
        }
    let myProducer = producer(); // nothing happens, generator object is returned
    myProducer.next(); // will pause on yield on (A)
    myProducer.next(); // will continue, and execute (B). Generator is done.

Tips

  • Usually loops within generator to continue "infinitely"
  • No return = implicit return undefined
  • Return value is ignored when used as iterable
  • yield yields an object with properties value and done
  • Recursive call with yield*
  • Not possible to yield inside callback

Resources

  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
  • Look for: iterator, generator, function*, yield, yield*
  • http://www.2ality.com/2015/03/es6-generators.html
  • http://gajus.com/blog/2/the-definitive-guide-to-the-javascript-generators
  • http://tobyho.com/2013/06/16/what-are-generators/

Template strings

`Unleash the power of backticks`

Embedded expressions

							// The good oldfashioned way
var a = 5;
var b = 10;
console.log('The sum of ' + a + ' and ' + b + ' is ' + (a + b));

// With template strings
console.log(`The sum of ${a} and ${b} is ${a + b}`);
						

Multi-line

							// The good oldfashioned way
console.log('Thank you Mario!\n' + 
    'But our princess is in another castle.');

// With template strings
console.log(`Thank you Mario!
But our princess is in another castle.`);
						

Tagged template strings

							var a = 5;
var b = 10;

function tag(strings, value1, value2) {
    console.log(strings[0]); // 'Hello '
    console.log(strings[1]); // ' world '
    console.log(value1);     // 15
    console.log(value2);     // 50

    return 'Foo!';
}

tag`Hello ${ a + b } world ${ a * b }`;
// 'Foo!'
						

References

Template Strings on Mozilla Developer Network

Code tasks

1. Variable substitution

In `src/04-template-strings/01-variable-substitution.js´ change the log() method to use embedded expressions instead of concatenated strings.

2. Multi-line strings

In `src/04-template-strings/02-multiline-strings.js´, remove all string concatenations and '\n' characters, and use a multi-line string with embedded expressions instead.

Maps and Sets

New data structures

Traditional key-value pairs

							var capitals = {
    'Norway': 'Oslo',
    'Sweden': 'Stockholm'
};
capitals['Denmark'] = 'Copenhagen';

console.log(capitals['Sweden']); 
// 'Stockholm'
						

Some problems

							// Iteration is a bit awkward
Object.keys(capitals).forEach(key => {
    console.log('The capital of ' + key + ' is ' + capitals[key]);
});

// The easiest way to get the size is
var size = Object.keys(capitals).length;

// Keys can only be represented as strings
capitals[1] = 'London';    // 1 is converted to '1'
capitals[true] = 'Berlin'; // true is converted to 'true'
						

Introducing Map

							// Initialize the map
var map = new Map();

// Add entry with key 'Norway' and value 'Oslo'
map.set('Norway', 'Oslo');

// Keys can be any data type, object, or even a function
var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};

map.set(keyString, 'value');
map.set(keyObj, 'value');
map.set(keyFunc, 'value');
						

Iteration and size

							// Iteration is a bit cleaner
for (let [country, capital] of capitals) {
    console.log('The capital of ' + country + ' is ' + capital);
}

// Getting the size is easy
var size = capitals.size;
						

Signs that you should be using Map instead of object

  • Keys are not known until run time
  • You frequently need to add/remove key-value pairs
  • You need to use keys that are not strings
  • You want to iterate the collection

Set introduction

							// Initialize the set
var set = new Set();

// Add value
set.add('Norway');

// No duplicate values
set.add('Norway'); // has no effect

// Values can be any data type, object, or a function
var obj = {},
    func = function () {};

set.add(obj);
set.add(func);
						

Set operations

							// Simple iteration
for (let value of set) {
    console.log(value);
}

// Get size
var size = set.size;

// Check if value exists
var exists = set.has('Norway');

// Delete
set.delete('Norway');
						

WeakMap

  • Similar to Map, but keys are weakly referenced
  • Allows keys to be garbage collected
  • Only objects can be used as keys
  • Not possible to iterate, list keys, or get size

WeakMap usage

							let map = new WeakMap();

let apple = { id: 1, name: 'Apple' };
map.set(apple, 3);
map.get(apple); // 3

// Will not be kept in the map, as no one else is holding a 
// reference to the key
map.set({ id: 2, name: 'Orange' }, 5); 

// Will be held in the map until the element with id 'example' 
// is removed from DOM
map.set(document.getElementById('example'), { clickCount: 4 });
						

WeakSet

  • Similar to Set, but values are weakly referenced
  • Allows values to be garbage collected
  • Only objects can be used as values
  • Not possible to iterate, list values, or get size

Example use case for WeakSet

							// Simple tagging of DOM elements that have been touched/edited.
// When elements are removed from DOM, the garbage collector
// will automatically remove them from the Set.

var touchedElements = new WeakSet();

function addTouched(domElement) {
    touchedElements.add(domElement);
}

function hasBeenTouched(domElement) {
    return touchedElements.has(domElement);
}
						

Code tasks

1. Map

In `src/07-maps-and-sets/01-map.js´, rewrite carMap to be a Map, and modify the functions so that they work with the Map data structure.

2. Set

In `src/07-maps-and-sets/02-set.js´, change getUniqueActors() to use a Set in order to return a unique array of actors.

THE END

ECMAScript Workshop