Notes from the Wes Bos course at ES6.io
- Function scope vs block scope
- Block scope as replacement of IIFE
- Arrow functions don't rebind 'this'
- Parenthesis needed around object literals for implicit return in arrow functions
- Top-level functions on event handlers typically use non-arrow functions because rebinding 'this' is desired *
- Inside event handlers though, arrow functions are great because 'this' can usefully refer to the event triggering element
- Default function arguments (9)
- Ternary with conditional logic for template literal (13)
- Render function within a template literal (13)
- Tagged template literals (14)
- Assembling a tagged template literal programmatically with reduce (15 + 16)
- String methods: startsWith, endsWith, includes (17)
- Destructuring, renaming while destructuring, default values (18)
- Rest operator (19)
- Swapping variables through destructuring (20)
- Returning multiple values through destructuring (21) *
- Named defaults from an empty array (21)
- For-of loop, break, continue, entries (22)
- Second parameter of Array.from() runs map() (25)
- Array.find() + Array.findIndex() (26)
- Spread operators as function arguments (31)
- Rest operators as function parameters (32)
- Updated key syntax on object literals (33)
- Symbols (38)
- ESLint + Git Hooks (44)
- Default exports vs named exports in JS modules (45)
- Babel "env" setting in .babelrc (49)
- Static methods on classes (52)
- Proxies, handlers, traps (58)
- Sets + WeakSets (61)
- Garbage collection (63)
- Maps used for metadata collection on an object (64)
- Promises + async/await (69)
- Async/await error handling with higher-order functions (70) *
- Class properties (73)
- Array.includes() (75)
- Trailing commas (76)
- Object.entries() + Object.values() (77)
- Curly brackets have many meanings
- object literals
- destructuring variables out of an object
- importing a named export
var
- Function scope: Available inside function where a var is created (local variable)
- Or globally scoped if not in a function
- Global scoped is generally not what we want. To update a variable outside a function we should return from the function
- if{} blocks are not functions and will leak var variable scope. This isn't intuitive and exemplifies one of the big upgrades we get with block scope
var age = 100;
if(age > 12) {
var dogYears = age * 7;
console.log(`You are ${dogYears} dog years old!`);
}
console.log(dogYears) //700
let
- Block scope: Available inside block where a let is created
- if{} blocks will constrain scope
var age = 100;
if(age > 12) {
let dogYears = age * 7;
console.log(`You are ${dogYears} dog years old!`);
}
console.log(dogYears) //undefined
In contrast to var, let and const cannot be redeclared in the same scope.
let points = 50;
let winner = false;
let points = 40; // error
Object.freeze()
The Object.freeze() method freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed, it also prevents the prototype from being changed.
IIFE
- Thanks to block scope, blocks + let/const constrain scope just like an IIFE
{
const name = 'wes';
}
...can replace...
(function(){
const name = 'wes';
})()
for loop
- The block scope of let prevents the variable in a for loop from escaping into global scope.
- Also creates nine scopes in the example below that all retain their declared value, in contrast to var which would be overridden and simply output the final value 10 times
for(let i = 0; i < 10; i++) {
setTimeout(function() {
console.log('The number is ' + i);
},1000);
}
- var can be accessed prior to being declared, although it's value is undefined
console.log(pizza); //undefined because in the TEMPORAL DEAD ZONE
var pizza = 'Deep Dish 🍕🍕🍕';
- let/const cannot be accessed before being declared
console.log(pizza); //Error
const pizza = 'Deep Dish 🍕🍕🍕';
- Most familiar to me:
- use const by default
- only use let if rebinding is needed
- (var shouldn’t be used in ES2015)
https://mathiasbynens.be/notes/es6-const
Some benefits of arrow functions
- Concise
- Implicit returns
const fullNames4 = names.map(name => `${name} bos`);
vs
const fullNames3 = names.map(name => {
return `${name} bos`;
});
-
Doesn't rebind the value of this
-
Arrow functions are always anonymous functions, but they can be declared in a variable
const sayMyName = (name) => {
alert(`Hello ${name}!`)
}
One benefit of a named function is that the name can be helpful in tracking errors in a stack trace
- Implicit return with object literal
- Removing function block braces works for other types of returned values, but gets weird with an object literal, which has its own braces
Solution: parenthesis around object literal:
const win = winners.map((winner, i) => ({name: winner, race, place: i + 1}));
Nice example of implicit return of a boolean
const ages = [23,62,45,234,2,62,234,62,34];
const old = ages.filter(age => age >= 60);
- When using an arrow function, the value of this is not rebound, so can end up with parent scope on a click event handler.
const box = document.querySelector('.box');
box.addEventListener('click', () => console.log(this)) //Window NOT box!
- Generally good then to have a non-arrow function at the top-level of an event handler because we want this to establish a specific context relative to the element associated with triggering the event.
const box = document.querySelector('.box');
box.addEventListener('click', function() {console.log(this);}) // Box!
- Arrow function inherits value of this from parent function, which is useful inside a callback.
const box = document.querySelector('.box');
box.addEventListener('click', function() {
this.classList.toggle('opening');
setTimeout(() => {
console.log(this); // Still box, but would be Window if non-arrow function! bc new context unbound to box would be created by non-arrow function
this.classList.toggle('open');
}, 500);
});
Inheriting this via arrow functions allows you to achieve the same thing as
var that = this;
function calculateBill(total, tax = 0.13, tip = 0.15) {
return total + (total * tax) + (total * tip);
}
const totalBill = calculateBill(100, undefined, 0.25);
- undefined is still needed as a parameter in the function invocation, which seems odd to me
Can replace:
tax = tax || 0.13;
(1) Top level of an event handler callback where 'this' is needed
✅
const button = document.querySelector('#pushy');
button.addEventListener('click', function() {
console.log(this); //button
this.classList.toggle('on');
});
⛔
const button = document.querySelector('#pushy');
button.addEventListener('click', () => {
console.log(this); //Oh no, Window!
this.classList.toggle('on');
});
(2) When you need a method to bind to an object
✅
const person = {
points: 23,
score() {
console.log(this); // person {...}
this.points++; // Increments object attribute
}
// ^ SAME AS ^
// score: function() {}
}
⛔
const person = {
points: 23,
score: () => {
console.log(this); // Oh no, Window!
this.points++; // Undefined
}
}
(3) When you need to add a prototype method
✅
class Car {
constructor(make, colour) {
this.make = make;
this.colour = colour;
}
}
const beemer = new Car('bmw', 'blue');
const subie = new Car('Subaru', 'white');
Car.prototype.summarize = function() {
return `This car is a ${this.make} in the colour ${this.colour}`;
};
beemer.summarize() //"This car is a bmw in the colour blue"
⛔
class Car {
constructor(make, colour) {
this.make = make;
this.colour = colour;
}
}
const beemer = new Car('bmw', 'blue');
const subie = new Car('Subaru', 'white');
Car.prototype.summarize = () => {
return `This car is a ${this.make} in the colour ${this.colour}`;
};
beemer.summarize() // "This car is a undefined in the colour undefined"
(4) When you need arguments object
✅
const orderChildren = function() {
// orderChildren('beyonce', 'solange')
console.log(arguments); // ["beyonce", "solange"]
const children = Array.from(arguments);
return children.map((child, i) => {
return `${child} was child #${i + 1}`;
})
}
⛔
const orderChildren = () => {
// orderChildren('beyonce', 'solange')
console.log(arguments); // Uncaught ReferenceError: arguments is not defined
const children = Array.from(arguments);
return children.map((child, i) => {
return `${child} was child #${i + 1}`;
})
}
My answer:
const videoSeconds = videoTimes.map(videoTime => {
const [minutes , seconds] = videoTime
.split(':')
.map(parseFloat);
return minutes * 60 + seconds;
});
- Declaration of minutes and seconds is happening via destructuring since the other side of the equals operator will have an array.
Bos answer:
.map(timecode => {
const parts = timecode.split(':').map(part => parseFloat(part));
return (parts[0] * 60) + parts[1];
})
const dogs = [
{ name: 'Snickers', age: 2 },
{ name: 'Hugo', age: 8 },
{ name: 'Sunny', age: 1 }
];
const markup = `
<ul class="dogs">
${dogs.map(dog => `
<li>
${dog.name}
is
${dog.age * 7}
</li>`).join('')}
</ul>
`;
- Possible to iterate and generate template literals inside another template literal.
const song = {
name: 'Dying to live',
artist: 'Tupac',
featuring: 'Biggie Smalls'
};
const markup = `
<div class="song">
<p>
${song.name} — ${song.artist}
${song.featuring ? `(Featuring ${song.featuring})` : ''}
</p>
</div>
`;
- Ternary within template literal to handle string that may or may not be present
const beer = {
name: 'Belgian Wit',
brewery: 'Steam Whistle Brewery',
keywords: ['pale', 'cloudy', 'spiced', 'crisp']
};
function renderKeywords(keywords) {
return `
<ul>
${keywords.map(keyword => `<li>${keyword}</li>`).join('')}
</ul>
`;
}
const markup = `
<div class="beer">
<h2>${beer.name}</h2>
<p class="brewery">${beer.brewery}</p>
${renderKeywords(beer.keywords)}
</div>
`;
${renderKeywords(beer.keywords)}
- Possible to delegate to a render function the work of generating a template literal 😍
- Parallels with React patterns where a component is designated to handle a specific bit of markup generation
function highlight(strings, ...values) {
debugger;
}
- Dev tools > sources > scope > local
- Useful way to assess a function's local scope
function highlight(strings, ...values) {
let str = '';
strings.forEach((string, i) => {
str += `${string} <span contenteditable class="hl">${values[i] || ''}</span>`;
});
return str;
}
const name = 'Snickers';
const age = 100;
const sentence = highlight`My dog's name is ${name} and he is ${age} years old`;
- Pass in the string and the tagged template literal is broken into its pieces, which can then be manipulated as they are reassembled
- rest spread useful in accepting as many arguments as are passed in
- Suppose this is how you could make something like "drunk-ify text"
const dict = {
HTML: 'Hyper Text Markup Language',
CSS: 'Cascading Style Sheets',
JS: 'JavaScript'
};
function addAbbreviations(strings, ...values) {
const abbreviated = values.map(value => {
if(dict[value]) {
return `<abbr title="${dict[value]}">${value}</abbr>`;
}
return value;
});
return strings.reduce((sentence, string, i) => {
return `${sentence}${string}${abbreviated[i] || ''}`;
}, '');
}
const first = 'Wes';
const last = 'Bos';
const sentence = addAbbreviations`Hello my name is ${first} ${last} and I love to code ${'HTML'}, ${'CSS'} and ${'JS'}`;
const abbreviated = values.map(value => {
if(dict[value]) {
return `<abbr title="${dict[value]}">${value}</abbr>`;
}
return value;
});
- Mapping over all values in template literal and testing if a given value can serve as key in the dict object
- If not, just returning the value
return strings.reduce((sentence, string, i) => {
return `${sentence}${string}${abbreviated[i] || ''}`;
}, '');
- Assembling a string programmatically from several sources using reduce
- XXS = Cross-site Scripting
- Security risk in using innerHTML w/o sanitizing as evil JS could get run—as in onload below
function sanitize(strings, ...values) {
const dirty = strings.reduce((prev, next, i) => `${prev}${next}${values[i] || ''}`, '');
return DOMPurify.sanitize(dirty);
}
const first = 'Wes';
const aboutMe = `I love to do evil <img src="http://unsplash.it/100/100?random" onload="alert('you got hacked');" />`;
const html = sanitize`
<h3>${first}</h3>
<p>${aboutMe}</p>
`;
const bio = document.querySelector('.bio');
bio.innerHTML = html;
const dirty = strings.reduce((prev, next, i) => `${prev}${next}${values[i] || ''}`, '');
- Seems the minimal way to reconstruct from a tagged template using reduce
return DOMPurify.sanitize(dirty);
- DOMPurify is imported
Four new methods
- Largely conveniences to avoid reliance on regex
- Case sensitivity is a bit of a drawback compared to regex
const course = 'RFB2';
const flightNumber = '20-AC2018-jz';
const accountNumber = '825242631RT0001';
.startsWith()
course.startsWith('RF'); //true
course.startsWith('rf'); //false
- Cannot make case insensitive
.endsWith()
flightNumber.endsWith('jz'); //true
accountNumber.endsWith('RT'); //false
accountNumber.endsWith('RT', 11); //true
- Second parameter determines how much of the original string to consider
- Here the first 11 characters
.includes()
flightNumber.includes('AC'); //true
flightNumber.includes('ac'); //false
- Backstory on contains() + MooTools name space
.repeat()
function leftPad(str, length = 20) {
return `→ ${' '.repeat(length - str.length)}${str}`;
}
- Repeats a given string as many times as specified in the parameter
- In this case, repeat is used to right align text by inserting spaces programatically
- Key syntax point is to match the left and right sides:
const { A, B } = { A: 'value1', B: 'value2' };
const [ A, B ] = ['value1', 'value2'];
In both cases:
console.log(A); //"value1"
console.log(typeof(A)); //string
Object destructuring syntax
const person = {
first: 'Wes',
last: 'Bos',
country: 'Canada',
city: 'Hamilton',
twitter: '@wesbos'
};
const { first, last, twitter } = person;
const { } = person;
- The brackets here are destructuring syntax, not a block or an object.
- When destructuring an object, we use curly brackets. But when destructuring an array, we'll use square brackets.
- A bit like a type hint
- Top-level variable
Destructuring deeply nested data
const wes = {
first: 'Wes',
last: 'Bos',
links: {
social: {
twitter: 'https://twitter.com/wesbos',
facebook: 'https://facebook.com/wesbos.developer',
},
web: {
blog: 'https://wesbos.com'
}
}
};
const { twitter, facebook } = wes.links.social;
- With nested data
Renaming
const { twitter: tweet, facebook: fb } = wes.links.social;
- Can rename as you destructure
- Otherwise, we're a bit "trapped" because "twitter" is the key we need to use
- Here the variable names are tweet and fb
Setting defaults
const settings = { width: 300, color: 'black' }
const { width = 100, height = 100, color = 'blue', fontSize = 25} = settings;
- If the value exists in settings then it will override the default
Combined example
// Object destructuring with variable renaming & default values
const { w: width = 400, h: height = 500 } = { w: 800 }
- Not necessarily a common use case, but models all concepts
Array destructuring syntax
const details = ['Wes Bos', 123, 'wesbos.com'];
const [name, id, website] = details;
- Uses square brackets, as opposed to curly brackets for objects
const data = 'Basketball,Sports,90210,23,wes,bos,cool';
const [itemName, category, sku, inventory] = data.split(',');
- Creating an array from a string with split(), and then immediately destructuring into the four desired variables
const team = ['Wes', 'Harry', 'Sarah', 'Keegan', 'Riker'];
const [captain, assistant, ...players] = team;
console.log(players) // ['Sarah', 'Keegan', 'Riker'] ;
- Rest operator allows us to get all the remaining items
let inRing = 'Hulk Hogan';
let onSide = 'The Rock';
console.log(inRing, onSide); // 'Hulk Hogan', 'The Rock'
[inRing, onSide] = [onSide, inRing];
console.log(inRing, onSide); // 'The Rock', 'Hulk Hogan'
[inRing, onSide] = [onSide, inRing];
- On the right, creating an array which is immediately destructured into the two assignments on the left
Multiple returns
function convertCurrency(amount) {
const converted = {
USD: amount * 0.76,
GPB: amount * 0.53,
AUD: amount * 1.01,
MEX: amount * 13.30
};
return converted;
}
const { USD, GBP } = convertCurrency(100);
- While we're technically still just returning the single object, this effectively allows multiple values from a single function return
Named defaults
function tipCalc({ total = 100, tip = 0.15, tax = 0.13 } = {}) {
return total + (tip * total) + (tax * total);
}
const bill = tipCalc({ tip: 0.20, total: 200 });
- Versatile syntax allowed by passing an object into a function
- Order of values passed in is not important
- Don't need to pass in undefined for a parameter value like we did in video 5
- Empty object is a bit unintuitive as a sort of second-tier default, but needed in case no values are passed in as parameters
Built-in JS iterables
String, Array, TypedArray, Map and Set are all built-in iterables, because their prototype objects all have a Symbol.iterator method.
for-of loop
- Can be used with any kind of data except objects
- Allows break and continue
const cuts = ['Chuck', 'Brisket', 'Shank', 'Short Rib'];
for (const cut of cuts) {
if(cut === 'Brisket') {
continue;
}
console.log(cut);
}
- Existing methods of iterating and their drawbacks
- for loop
- Scope issue with var, slightly chunky syntax
- forEach
- No way to abort a loop with something like break
- No way to skip over with continue
- for-in loop
- Weirdly, will also iterate over anything added to the prototype, not only the items in the iterable collection
- 3rd party code might extend prototype (MooTools)
- for loop
for (const [i, cut] of cuts.entries()) {
console.log(`${cut} is the ${i + 1} item`);
}
- entries is immediately destructured into two variables
Array.prototype.entries(): The entries() method returns a new Array Iterator object that contains the key/value pairs for each index in the array.
function addUpNumbers() {
let total = 0;
for (const num of arguments) {
total += num;
}
return total;
}
addUpNumbers(10,23,52,34,12,13,123);
- for-of loop with arguments allows us to accept any number of parameters
- Best practice would be to convert arguments to an array, but if just iterating over then not a problem to forgo conversion
const ps = document.querySelectorAll('p');
for (const paragraph of ps) {
paragraph.addEventListener('click', function() {
console.log(this.textContent);
});
}
- I'd typically use forEach to add event listeners, but for-of might be useful if you wanted to use break or continue to more selectively apply event handlers
- Object.entries() is standard in ES2017
- Subject of video 77
const apple = {
color: 'Red',
size: 'Medium',
weight: 50,
sugar: 10
};
for (const prop in apple) {
const value = apple[prop];
}
- Short of Object.entries() Bos suggests just sticking with a for-in loop
- Neither are on the prototype, but on Array itself
Arrary.from()
<div class="people">
<p>Wes</p>
<p>Kait</p>
<p>Snickers</p>
</div>
const people = document.querySelectorAll('.people p');
const peopleArray = Array.from(people, person => person.textContent);
console.log(peopleArray); //["Wes", "Kait", "Snickers"]
- Second parameter runs map method 🆒
function sumAll() {
const nums = Array.from(arguments);
return nums.reduce((prev, next) => prev + next, 0);
}
sumAll(2, 34, 23, 234, 234, 234234, 234234, 2342);
- Common use case is to convert arguments to an array in order to use all the array methods, such as reduce above
- Note that sumAll needs to be a non-arrow function since it relies on accessing arguments
Arrary.of()
const ages = Array.of(12,4,23,62,34);
console.log(ages); // [12, 4, 23, 62, 34]
- Pretty straightforward
const posts = [
{
"code":"VBgtGQcSf",
"caption":"Trying the new Hamilton Brewery beer. Big fan.",
"likes":27,
"id":"1122810327591928991",
"display_src":"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e35/12224456_175248682823294_1558707223_n.jpg"
},
{
"code":"FpTyHQcau",
"caption":"I'm in Austin for a conference and doing some training. Enjoying some local brew with my baby.",
"likes":82,
"id":"1118481761857291950",
"display_src":"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/e35/11326072_550275398458202_1726754023_n.jpg"
}
];
const code = 'VBgtGQcSf';
const post = posts.find(post => post.code === code);
console.log(post); // { ENTIRE OBJECT THAT MATCHES THE CONDITION }
- find() returns once a condition is true
- If you wanted multiple results, then use filter()
const postIndex = posts.findIndex(post => post.code === code);
console.log(postIndex); // 22
- findIndex(): Useful if you want to do something like delete a post or apply a highlight class
- Not a part of ES6, but included because useful and somewhat rare "in the wild"
const ages = [32, 15, 19, 12];
// 👵👨 is there at least one adult in the group?
const adultPresent = ages.some(age => age >= 18);
console.log(adultPresent); / /true
// 🍻 is everyone old enough to drink?
const allOldEnough = ages.every(age => age >= 19);
console.log(allOldEnough); // false
console.log([...'wes']); //["w", "e", "s"]
const featured = ['Deep Dish', 'Pepperoni', 'Hawaiian'];
const specialty = ['Meatzza', 'Spicy Mama', 'Margherita'];
console.log([...featured, ...specialty]);
const pizzas = [...featured, 'veg', ...specialty];
const fridayPizzas = [...pizzas];
- Nice alternative to .concat()
- Spread operator also allows an array to be copied instead of referenced
const fridayPizzas = pizzas;
fridayPizzas[0] = 'Yum-1';
console.log(pizzas[0]) // 'Yum-1' - OH NO;
- Referencing an array is just bad.
<h2 class="jump">SPREADS!</h2>
Wes:
const heading = document.querySelector('.jump');
heading.innerHTML = sparanWrap(heading.textContent);
function sparanWrap(word) {
return [...word].map(letter => `<span>${letter}</span>`).join('');
}
Me:
const target = document.querySelector('.jump');
target.innerHTML = [...target.textContent].map(letter => `<span>${letter}</span>`).join('')
- Separating the logic out into its own function makes Wes's solution more reusable and easier to reason about
const people = Array.from(document.querySelectorAll('.people p'));
const people = [...document.querySelectorAll('.people p')];
- Fair point that Array.from() reads more clearly to others
const comments = [
{ id: 209384, text: 'I love your dog!' },
{ id: 523423, text: 'Cuuute! 🐐' },
{ id: 632429, text: 'You are so dumb' },
{ id: 192834, text: 'Nice work on this wes!' },
];
const id = 632429;
const commentIndex = comments.findIndex(comment => comment.id === id);
const newComments = [...comments.slice(0,commentIndex), ...comments.slice(commentIndex + 1)];
Spread operators can be useful as arguments passed into functions
const inventors = ['Einstein', 'Newton', 'Galileo'];
const newInventors = ['Musk', 'Jobs'];
inventors.push(...newInventors);
const name = ['Wes', 'Bos'];
function sayHi(first, last) {
alert(`Hey there ${first} ${last}`);
}
sayHi(...name);
- Simply saying, pass in all elements of the name array
Looks the exact same as spread operator but behaves differently as a function parameter
function convertCurrency(rate, ...amounts) {
return amounts.map(amount => amount * rate);
}
const amounts = convertCurrency(1.54, 10, 23, 52, 1, 56);
const runner = ['Wes Bos', 123, 5.5, 5, 3, 6, 35];
const [name, id, ...runs] = runner;
console.log(name, id, runs); // Wes Bos 123 [5.5, 5, 3, 6, 35]
const team = ['Wes', 'Kait', 'Lux', 'Sheena', 'Kelly'];
const [captain, assistant, ...players] = team;
console.log(captain, assistant, players) // Wes Kait ["Lux", "Sheena", "Kelly"]
If key has same name as variable can omit colon:
const first = 'snickers';
const last = 'bos';
const age = 2;
const breed = 'King Charles Cav';
const dog = {
firstName: first,
last,
age,
breed,
pals: ['Hugo', 'Sunny']
};
Simple modal syntax removes need for colon and function keyword:
const modal = {
create(selector) {
},
open(content) {
},
close(goodbye) {
}
}
- Can pass in parameter:
Previously would need to use:
const modal = {
create: function(selector) {
}
Can dynamically set keys on an object:
const key = 'pocketColor';
const value = '#ffc600';
const tShirt = {
[key]: value,
[`${key}Opposite`]: invertColor(value)
};
Computed property keys are possible in ES6:
const keys = ['size', 'color', 'weight'];
const values = ['medium', 'red', 100];
const shirt = {
[keys.shift()]: values.shift(),
[keys.shift()]: values.shift(),
[keys.shift()]: values.shift(),
}
const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');
postsPromise
.then(data => data.json())
.then(data => { console.log(data) })
.catch((err) => {
console.error(err);
})
- fetch() returns a promise
- At time of declaration postPromise is like an IOU
- Think of then() like a callback that allows for synchronous execution (once the IOU returns)
- .json() method is key to translating returned data into useful JSON
Can think of a promise as: "I don't want to stop JS from running. I just want to start this thing, and once it comes back pick it up"
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(Error('Err wes isn\'t cool'));
}, 1000);
});
myPromise
.then(data => {
console.log(data);
})
.catch(err => {
console.error(err);
});
- resolve() + reject() methods come with the promise
- Using an Error object in reject() allows the stack trace to locate the line where the error was thrown
const posts = [
{ title: 'I love JavaScript', author: 'Wes Bos', id: 1 },
{ title: 'CSS!', author: 'Chris Coyier', id: 2 },
{ title: 'Dev tools tricks', author: 'Addy Osmani', id: 3 },
];
const authors = [
{ name: 'Wes Bos', twitter: '@wesbos', bio: 'Canadian Developer' },
{ name: 'Chris Coyier', twitter: '@chriscoyier', bio: 'CSS Tricks and CodePen' },
{ name: 'Addy Osmani', twitter: '@addyosmani', bio: 'Googler' },
];
function getPostById(id) {
return new Promise((resolve, reject) => {
// using a settimeout to mimick a databse
setTimeout(() => {
const post = posts.find(post => post.id === id);
(post)
? resolve(post)
: reject(Error('No Post Was Found!'));
}, 1000)
});
}
function hydrateAuthor(post) {
return new Promise((resolve, reject) => {
const authorObject = authors.find(author => author.name === post.author);
if(authorObject) {
// "hydrate" the post object with the author object
post.author = authorObject;
resolve(post)
} else {
reject(Error('Can not find the author'));
}
});
}
getPostById(1)
.then(post => {
return hydrateAuthor(post);
})
.then(post => {
console.log(post);
})
.catch(err => {
console.error(err);
});
- debugger -> sources -> scope
- Kind of like console.log but with everything in the given scope
- I suppose flow control means using promises to synchronously execute functions?
- I'm using a ternary above, but Bos uses a plain if-else, which might make more sense since the second promise will use if-else
const weather = new Promise((resolve) => {
setTimeout(() => {
resolve({ temp: 29, conditions: 'Sunny with Clouds'});
}, 2000);
});
const tweets = new Promise((resolve) => {
setTimeout(() => {
resolve(['I like cake', 'BBQ is good too!']);
}, 500);
});
Promise
.all([weather, tweets])
.then(responses => {
const [weatherInfo, tweetInfo] = responses;
console.log(weatherInfo, tweetInfo)
});
- Promise.all() accepts an array of promises and waits for them all to resolve
const [weatherInfo, tweetInfo] = responses;
- Great use case for destructuring as a means to declare const variables
const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');
const streetCarsPromise = fetch('http://data.ratp.fr/api/datasets/1.0/search/?q=paris');
Promise
.all([postsPromise, streetCarsPromise])
.then(responses => {
return Promise.all(responses.map(res => res.json()))
})
.then(responses => {
console.log(responses);
});
- With Promise.all(), we still need to convert the response to JSON via a second Promise.all() invocation
- Might consider global install of Browsersync to get beyond the CORS limitation fetch() to an actual API often will present
-
7th primitive type and a new primitive in JS
-
Previous 6: number, string, object, boolean, null, undefined
-
Can help avoid naming conflicts, but not really a highly demanded addition
-
Could be useful if you want to create a property in a unique way
const wes = Symbol('Wes'); const person = Symbol('Wes'); console.log(wes === person) // false;
-
Pass in a descriptor (here 'Wes')
-
Symbol is a unique identifier
-
Actual symbol is something like a 40-character random string, and the descriptor just represents it for ease of reading
const classRoom = {
[Symbol('Mark')] : { grade: 50, gender: 'male' },
[Symbol('Olivia')]: { grade: 80, gender: 'female' },
[Symbol('Olivia')]: { grade: 83, gender: 'female' },
};
for (const person in classRoom) {
console.log(person); //Nothing!
}
- Symbols cannot be iterated over
- Good example since people in a group need a unique identifier but could have the same name
- Ensures keys in an object are absolutely unique
- Potentially useful for storing private data?
const syms = Object.getOwnPropertySymbols(classRoom);
const data = syms.map(sym => classRoom[sym]);
console.log(data);
- Object.getOwnPropertySymbols() pass in object and returns array of symbols, which can then be iterated over
- Weird work around
npm install -g eslint
- Globally installing ESLint
.eslintrc
"env": what environments will you use the JS in
"env": {
"es6": true,
"browser": true
},
"rules": overrides to the extended set of rules
"rules": {
"no-console": 0,
"no-unused-vars": 1
},
- 0 = off, 1 = warn, 2 = error
https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb
eslint bad-code.js --fix
-
CLI allows easy fixes to be auto corrected with --fix flag
-
.eslintrc files in a project directory work well to allow each project/team its own conventions, but it's also useful to make a global .eslintrc that can serve as a default
atom ~/.eslintrc
- System file in home directory (~)
- This will be overridden by a project specific .eslintrc
- ESLint will use the settings in the closest .eslintrc file. So the closest parent folder with an .eslintrc will be used before the settings in a file in the home directory
/* globals twttr ga */
...
ga.track();
twttr.trackConversion();
- globals keyword in comment block at head of file allows global variables
/* eslint-disable no-extend-native */
if (!Array.prototype.includes) {
...
}
/* eslint-enable no-extend-native */
/* eslint-disable */
if (!Array.prototype.includes) {
...
}
/* eslint-enable */
- Can disable and then re-enable a specific rule, or all linting
In .eslintrc
"plugins": ["html", "markdown"]
-
These allow ESLint to run on script tags inside HTML (and ignore all the HTML markup) and on JS in markdown notes
-
Might not be able to run --fix flag in HTML or markdown
- Git hook = prevent code that doesn't pass a condition from being commited
- /.get/hooks
.git/hooks/commit-msg.sample
- Remove .sample from file name
- Replace sample text in that file with Bos's script in 12 - Code Quality with ESLint/commit-msg.txt
- This will prevent a commit to that repo if ESLint has errors
Decent demo but Webpack and Babel versions need to be tied to earlier releases.
- Need to look into "zero-config" Webpack 4 for current standards
- es6-module-instructions.md have packages, etc.
- Big picture is that modules are files with one or more functions that can be made available to other modules. We want to write code in modules, and Webpack provides the bundler to assemble all the modules together
(1) Make entry point in app.js
(2) Create package.json + install modules + add run scripts
-
npm init
-
npm i -s lodash
-
npm i -D lodash
- --save-dev (-D) = tool for development and not a part of the application we ship client side
-
In package.json
"scripts": { "build": "webpack --progress --watch" },
(3) Make webpack.config file
In webpack.config.js
const webpack = require('webpack');
- We use CommonJS require here because Node doesn't deal with ES6 import syntax
- Interesting to note that Webpack is running in Node rather than in the browser. Makes sense as Webpack is bundling all the code before it is delivered to the browser
const nodeEnv = process.env.NODE_ENV || 'production';
- When we make a production build, it will be much smaller than our development build.
module: {
loaders: [
]
}
- Knowing that it can be used to accept almost anything, loaders answers: How should Webpack handle different file types?
- Good to note that modules like these could also be published to npm, which is what we're doing at work.
- Also, variables are scoped to their module
Two types of exports in ES6
Default exports
export default apiKey;
- The main thing a module needs to export
- Can be named anything on import
import apiKey from './src/config';
import alternateNameforApiKey from './src/config';
Named exports
export const apiKey = '123';
- For methods, variables, etc.
import { apiKey } from './src/config';
- Names must match between import and export
- The curly braces in the syntax for importing a named export look like destructuring, but are not.
import { apiKey as key } from './src/config';
- Renaming with as is useful if you've already got a variable using a particular namespace
const age = 100;
const dog = 'snickers';
export { age as old, dog };
- Can export multiple things and rename
export default function User(name, email, website) {
return { name, email, website };
}
- Nice model of object literal same-name key-value syntax
import User, { createURL, gravatar } from './src/user';
- Importing the default export as well as some named exports
import slug from 'slug';
import { url } from './config';
import md5 from 'md5';
export default function User(name, email, website) {
return { name, email, website };
}
export function createURL(name) {
return `${url}/users/${slug(name)}`;
}
export function gravatar(email) {
const hash = md5(email.toLowerCase());
const photoURL = `https://www.gravatar.com/avatar/${hash}`;
return photoURL;
}
- Nice little example of a tidy module
https://github.com/systemjs/systemjs
Nice model of a quick way to set up module bundling w/o Webpack overhead
- Not for production because too slow, but useful for a quick exercise
<script src="https://jspm.io/system@0.19.js"></script>
<script>
System.config({ transpiler: 'babel' });
System.import('./main.js');
</script>
import { sum, kebabCase } from 'npm:lodash';
console.log(kebabCase('Wes is soooo cool ⛓⛓⛓⛓'));
- Note: npm:lodash
"scripts": {
"server": "browser-sync start --directory --server --files '*.js, *.html, *.css'"
},
- BrowserSync start script
- Just need to save-dev BrowserSync
- If you're not using modules, can transpile via Babel w/o Webpack
npm install babel-cli@next
Allows use of Babel 7 features
"dependencies": {
"babel-cli": "^7.0.0-beta.1",
"babel-preset-env": "^2.0.0-beta.1"
},
"scripts": {
"babel": "babel app.js --watch --out-file app-compiled.js"
},
"babel": {
"presets": [
[
"env",
{
"targets": {
"browsers": [
"last 2 versions",
"safari >= 8"
]
}
}
]
]
},
- The "babel" property in a package.json can accept all the same settings as a .babelrc
- "presets" > "env" allows babel to manage the scope of transpiling required to to support designated targets.
- As browser's change, this will automatically adapt
- Very similar to auto-prefixer
- "env" may be a wiser choice than "es2015" because it avoids unnecessarily transpiling JS that is already supported by most browsers https://babeljs.io/docs/plugins/preset-env/
"plugins": [
"transform-object-rest-spread"
]
<script src="https://cdn.polyfill.io/v2/polyfill.js"></script>
- Babel accounts for the syntax changes in ES6, but new methods like Arrary.from() are not transpiled
- They need a polyfill
Two options
- babel-polyfill
- https://babeljs.io/docs/usage/polyfill/
- polyfill.io
- https://polyfill.io/v2/docs/
- Pretty cool as it dynamically detects a browser's user agent and polyfills only as needed
- May have less "code overhead" than the Babel polyfill since it only loads what it needs
ES6 brought classes into JavaScript, but prototypal inheritance has long been present in JS
function Dog(name, breed) {
this.name = name;
this.breed = breed;
}
- Capital 'D' because this is a constructor function
Dog.prototype.bark = function() {
console.log(`Bark, from ${this.name}`);
}
const duck = new Dog('Duck', 'Poodle')
Class declaration
class Dog {
}
- Preferred syntax
Class expression
const Dog = class {
}
- Some use cases, but generally not as common
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
static info() {
console.log(`This text should not be accessible on any instance`);
}
bark() {
console.log(`Bark, from ${this.name}`);
}
get description() {
return `${this.name} is a good dog`;
}
set nicknames(value) {
this.nick = value.trim();
}
get nicknames() {
return this.nick.toUpperCase();
}
}
const duck = new Dog('Duck', 'Poodle')
- constructor = what happens when class is invoked and an instance is created
- This ES6 syntax for a method without the word function was earlier modelled in video 33 on object literals
- A static method is not accessible on instances
- Kind of like it doesn't move from the class to instance, but stays static or in place
- Array.of() is another instance of a static method that is not inherited by other arrays off the prototype
duck.bark() // "Bark, from Duck"
duck.info() // Error
Dog.info() // "This text should not be accessible on any instance"
- A getter is not a method but a property that is computed
- A setter allows you to assign a property
class Animal {
constructor(name) {
this.name = name;
this.thirst = 100;
this.belly = [];
}
drink() {
this.thirst -= 10;
return this.thirst;
}
eat(food) {
this.belly.push(food);
return this.belly;
}
}
class Dolphasloth extends Animal {
constructor (name, chillFactor) {
super(name);
this.chillFactor = chillFactor;
}
slobber() {
this.thirst += 10;
this.chillFactor -= 10;
return this.thirst;
}
}
const ChillDude = new Dolphasloth('Chill Dude', 100)
-
First need to create an instance of Animal before we can access 'this' in an extended classes
- We can do this with super()
- super() will call whatever class you're extending
- Here super() needs name passed in because Animal() needs a name parameter for its constructor
- It runs the constructor function in the extended class, which in turn establishes this
super(name) // equals Animal(name)
-
Good to note that this is happening all the time in React
import React, { Component } from 'react';
export default class Battle extends Component {
static propTypes = {
match: PropTypes.object.isRequired,
}
...
}
Can extend any built-in primitives
class MovieCollection extends Array {
constructor(name, ...items) {
super(...items);
this.name = name;
}
add(movie) {
this.push(movie);
}
topRated(limit = 10) {
return this.sort((a, b) => (a.stars > b.stars ? -1 : 1)).slice(0, limit);
}
}
const movies = new MovieCollection('Wes\'s Fav Movies',
{ name: 'Bee Movie', stars: 10 },
{ name: 'Star Wars Trek', stars: 1 },
{ name: 'Virgin Suicides', stars: 7 },
{ name: 'King of the Road', stars: 8 }
);
movies.add({ name: 'Titanic', stars: 5 });
class MovieCollection extends Array {
constructor(name, ...items) {
super(...items);
- These three lines are pretty trippy
- The first ...items is a rest operator that accepts as many parameters as are passed in (here movie objects)
- The second ...items is a spread operator that essentially creates a new array with the items passed in (because super is invoking the Array primitive)
- new Array('1', '2', '3');
for (const movie of movies) {
console.log(movie);
}
- The new ES6 iteration method for...of is particularly useful here as the previous methods like for...in are more difficult to use resultant data from
NOTE: I get the sense that async functions are preferred over generators to handle promise-returning functions
- https://developers.google.com/web/fundamentals/primers/async-functions
- Introduced in video 20, seemingly as an update to the course
function* listPeople(){
yield 'Joe';
yield 'Abe';
yield 'Sam';
}
const people = listPeople();
console.log(people.next()); // {value: "Joe", done: false}
- A Generator has *, yield and next() syntax, which offers multiple returns
- done will be true once all the yield cases have been satisfied
const inventors = [
{ first: 'Albert', last: 'Einstein', year: 1879 },
{ first: 'Isaac', last: 'Newton', year: 1643 },
{ first: 'Galileo', last: 'Galilei', year: 1564 },
{ first: 'Marie', last: 'Curie', year: 1867 },
{ first: 'Johannes', last: 'Kepler', year: 1571 },
{ first: 'Nicolaus', last: 'Copernicus', year: 1473 },
{ first: 'Max', last: 'Planck', year: 1858 },
];
function* loop(arr) {
console.log(inventors);
for (const item of arr) {
yield item;
}
}
const inventorGen = loop(inventors);
- Here the first call of next() would log all the inventors and return/yield the first object ('Einstein')
- Then we'd need to call next() to step through the array.
- It's like the yield suspends within the for...of loop until done has a value of true
- One case for generators is ability to do waterfall-like Ajax requests
function ajax(url) {
fetch(url).then(data => data.json()).then(data => dataGen.next(data))
}
function* steps() {
console.log('fetching beers');
const beers = yield ajax('http://api.react.beer/v2/search?q=hops&type=beer');
console.log(beers);
console.log('fetching wes');
const wes = yield ajax('https://api.github.com/users/wesbos');
console.log(wes);
console.log('fetching fat joe');
const fatJoe = yield ajax('https://api.discogs.com/artists/51988');
console.log(fatJoe);
}
const dataGen = steps();
dataGen.next(); // kick it off
.then(data => dataGen.next(data))
- Calling next() here automatically sets up the next fetch
- So, you could include something returned from the first fetch in the query to a subsequent fetch assured that the first request would have returned before the second is issued
function* lyrics() {
yield `But don't tell my heart`;
yield `My achy breaky heart`;
yield `I just don't think he'd understand`;
}
const achy = lyrics();
for (const line of achy) {
console.log(line);
}
- for...of loop allows iteration through a generator and doesn't require .next()
const person = { personName: 'Wes', age: 100 };
const personProxy = new Proxy(person, {
get(target, name) {
return target[name].toUpperCase();
},
set(target, name, value) {
if(typeof value === 'string') {
target[name] = `${value.trim()} (✂️'d)`;
}
}
});
- Proxies allow us to jump in between and overwrite default operations
- First parameter is the target
- Second parameter is a custom handler with traps
- Good to note that the single-word method (get(), set()) is an example of the ES6 reduced syntax for methods on object literals demoed in video 33
- Prior to ES6 this syntax was get: function(target, name) {...}
personProxy.cool = " hella spaces here man "
console.log(personProxy.cool) // "HELLA SPACES HERE MAN (✂️'D)";
- The first dot notation above is the equivalent of calling set on the Proxy, which we trap in our handler and trim
- The second dot notation is the equivalent of calling get, which our trap transforms to uppercase
const phoneHandler = {
set(target, name, value) {
target[name] = value.match(/[0-9]/g).join('');
},
get(target, name) {
return target[name].replace(/(\d{3})(\d{3})(\d{4})/, '($1)-$2-$3');
}
}
const phoneNumbers = new Proxy({}, phoneHandler);
- Standardizes phone number values
const phoneNumbers = new Proxy({}, phoneHandler);
phoneNumbers.test = '344222-7522';
console.log(phoneNumbers.test) //"(344)-222-7522"
- Starting the proxy from an empty object "target"
- Passing in a handler object with various traps that override built-in methods
const safeHandler = {
set(target, name, value) {
const likeKey = Object.keys(target).find(k => k.toLowerCase() === name.toLowerCase());
if (!(name in target) && likeKey) {
throw new Error(`Oops! Looks like like we already have a(n) ${name} property but with the case of ${likeKey}.`);
}
target[name] = value;
}
};
const safety = new Proxy({ id: 100 }, safeHandler);
safety.ID = 200;
const safeHandler = {
set(target, name, value) {
const likeKey = Object.keys(target).find(k => k.toLowerCase() === name.toLowerCase());
- The handler, safeHandler, grabs the keys off the target object and uses the name parameter to access the key being used in the set operation
if (!(name in target) && likeKey) {
- (name in target) is new to me. Checks if an object has a key
const people = new Set();
people.add('Wes');
people.add('Snickers');
people.add('Kait');
console.log(people.size); // 3
people.delete('Wes') ;
people.clear();
- New primitive. Like an array, but no indexes and each item is unique
Set objects are collections of values. You can iterate through the elements of a Set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.
for (const person of people) {
console.log(person);
}
- Iterate over a set with for...of
- values() will return a SetIterator which has the same API as a generator, so can iterate with .next()
const students = new Set(['Wes', 'Kara', 'Tony']);
const dogs = ['Snickers', 'Sunny'];
const dogSet = new Set(dogs);
- Can create a Set by passing in an array
const brunch = new Set();
brunch.add('Wes');
brunch.add('Sarah');
brunch.add('Simone');
const line = brunch.values(); //Returns a generator
console.log(line.next().value); //Steps through the generator while logging the name at the current position, which is here Wes
console.log(line.next().value); // Sarah
console.log(brunch); //Set(3) {"Wes", "Sarah", "Simone"}
console.log(line); //SetIterator {"Simone"}
- When you call next() against a SetIterator the value at the current position will remove itself (mutation by default, weird)
- A WeakSet can only ever contain objects
- Cannot loop over a weak set (no iterator)
- Cannot use clear()
let dog1 = { name: 'Snickers', age: 3 };
let dog2 = { name: 'Sunny', age: 1 };
const weakSauce = new WeakSet([dog1, dog2]);
console.log(weakSauce);
dog1 = null;
console.log(weakSauce); //After waiting a few seconds, the weak set should have been garbage collected
- Weak sets clean themselves up through garbage collection
- By setting dog1 to null we've given the weak set a green light to garbage collect and automatically delete that value
- A memory leak is when you reference something that should not be able to be referenced but hasn't yet been garbage collected
- Hard to say exactly when garbage collection will occur because it varies between browsers and cannot be forced
- This may be useful in the case of removing DOM nodes that are no longer needed
The WeakSet is weak: References to objects in the collection are held weakly. If there is no other reference to an object stored in the WeakSet, they can be garbage collected. That also means that there is no list of current objects stored in the collection. WeakSets are not enumerable.
- Just as Sets are similar to arrays but with a twist, Maps are similar to objects (but with a twist)
const dogs = new Map();
dogs.set('Snickers', 3);
dogs.set('Sunny', 2);
dogs.set('Hugo', 10);
console.log(dogs.has('Snickers')); //true
console.log(dogs.get('Snickers')); //3
- Simple API
- has(), get()
dogs.forEach((val, key) => console.log(val, key));
for (const [key, val] of dogs) {
console.log(key, val);
}
- Two ways to iterate: forEach + for...of
for (const [key, val] of dogs) {
- Because each element in a Map returns an array with the key and value, we can use destructuring signalled by the square brackets here to declare these as variables being pulled out of the array
Use case here is storing metadata about an object, but avoiding putting the data on the object itself, which is a bit dirty and means all that data is erased if the object is erased. The metadata here is how many times the button has been clicked
const clickCounts = new Map();
const buttons = document.querySelectorAll('button');
buttons.forEach(button => {
clickCounts.set(button, 0);
button.addEventListener('click', function() {
const val = clickCounts.get(this);
clickCounts.set(this, val + 1);
});
});
- The keys on a Map can be objects, in this case a DOM node
- The enhanced feature over an object literal is that the key isn't limited to being a string
clickCounts.set(button, 0);
button.addEventListener('click', function() {
const val = clickCounts.get(this);
clickCounts.set(this, val + 1);
- get() and set() used this way still feels quite weird to me
- Because this refers to both the DOM node registering the click event and the Map key, it can be seen as an accurate identifier
let dog1 = { name: 'Snickers' };
let dog2 = { name: 'Sunny' };
const strong = new Map();
const weak = new WeakMap();
strong.set(dog1, 'Snickers is the best!');
weak.set(dog2, 'Sunny is the 2nd best!');
dog1 = null;
dog2 = null;
- The WeakMap here allows the deleted dog2 variable to be garbage collected
- This prevents a memory leak, where dog2 might be referenced even though it's been removed from our application. The "strong" Map, on the other hand, could expose the app to such a potential memory leak
- Bos says WeakMap is useful when you don't want to "babysit" what is included in your Map
- https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap
- How does the browser know that a given object or DOM node has no references to it and can therefor be garbage collected?
Also a great resource: https://developers.google.com/web/fundamentals/primers/async-functions
Two models of native promises
- Each use then() + catch()
fetch
fetch('https://api.github.com/users/wesbos').then(res => {
return res.json();
}).then(res => {
console.log(res);
}).catch(err => {
console.error('OH NOO!!!!!!!');
console.error(err);
})
- Fetch() is a native method that returns a promise
- Using console.error provides more of a stack trace
getUserMedia
const video = document.querySelector('.handsome'); //<video controls class="handsome"></video>
navigator.mediaDevices.getUserMedia({ video: true }).then(mediaStream => {
video.srcObject = mediaStream;
video.load();
video.play();
}).catch(err => {
console.log(err);
})
function breathe(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('That is too small of a value');
}
setTimeout(() => resolve(`Done for ${amount} ms`), amount);
});
}
breathe(1000).then(res => {
console.log(res);
return breathe(500);
}).then(res => {
console.log(res);
return breathe(600);
}).catch(err => {
console.error(err);
})
- We can write a function that returns a custom promise
- reject will throw an error, which needs to be caught with catch()
Almost all the time, JS is asynchronous, meaning the execution of code is not blocking other code from executing
Promises and Async + Await allow a degree of flow control
function breathe(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('That is too small of a value');
}
setTimeout(() => resolve(`Done for ${amount} ms`), amount);
});
}
async function go() {
console.log(`Starting`);
const res = await breathe(1000);
console.log(res);
const res2 = await breathe(300);
console.log(res2);
const res3 = await breathe(750);
console.log(res3);
const res4 = await breathe(900);
console.log(res4);
console.log('end');
}
go();
- Within an async function, we can use await which allows a promise to be resolved or rejected prior to moving on
Basic try/catch
async function go(firstName, lastName) {
try {
console.log(`Starting for ${firstName} ${lastName}`);
const res = await breathe(1000);
console.log(res);
const res2 = await breathe(100);
console.log(res2);
console.log('end');
} catch(err) {
console.error(err);
}
}
- Use case: You need to handle an error unique to this function
- For instance, in a form
Higher-order function for error handling
function breathe(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject('That is too small of a value');
}
setTimeout(() => resolve(`Done for ${amount} ms`), amount);
});
}
function catchErrors(fn) {
return function(...args){
return fn(...args).catch((err) => {
console.error(err);
});
}
}
async function go(firstName, lastName) {
console.log(`Starting for ${firstName} ${lastName}`);
const res = await breathe(1000);
console.log(res);
const res2 = await breathe(100);
console.log(res2);
console.log('end');
}
const wrappedFunction = catchErrors(go);
wrappedFunction('Joe', 'Winner');
- Use case: You need a general error handler that can be reused on various functions
- The wrapped function has error handling "sprinkled onto it" by the higher-order function
function catchErrors(fn) {
return function(...args){
return fn(...args).catch((err) => {
console.error(err);
});
}
}
- The first ...args is a rest operator and the second ...args is a spread operator
- This allows us to pass in any number of parameters to the wrapped function
We want multiple fetches to go out into the world rather than waiting for each one to return before the next is issued. So, this would most likely be bad:
async function go() {
const p1 = await fetch('https://api.github.com/users/wesbos');
const p2 = await fetch('https://api.github.com/users/stolinski');
Method one
async function go() {
const p1 = fetch('https://api.github.com/users/wesbos');
const p2 = fetch('https://api.github.com/users/stolinski');
const res = await Promise.all([p1, p2]);
const dataPromises = res.map(r => r.json());
const [wes, scott] = await Promise.all(dataPromises);
console.log(wes, scott);
// const people = await Promise.all(dataPromises); // Alternative if we don't know how many people being returned
}
go();
- Need two await Promise.all when handling fetched data
const [wes, scott] = await Promise.all(dataPromises);
- Destructing two immediately declared variables from the array returned by Promise.all()
Method two
async function getData(names) {
const promises = names.map(name => fetch(`https://api.github.com/users/${name}`).then(r => r.json()));
const people = await Promise.all(promises);
console.log(people);
}
getData(['wesbos', 'stolinski', 'darcyclarke']);
- Can chain the fetch() and then() calls together
- I like 👆
Related example from Jake Archibald:
async function logInOrder(urls) {
// fetch all the URLs in parallel
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// log them in sequence
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
https://developers.google.com/web/fundamentals/primers/async-functions
// navigator.geolocation.getCurrentPosition(function (pos) {
// console.log('it worked!');
// console.log(pos);
// }, function (err) {
// console.log('it failed!');
// console.log(err);
// });
function getCurrentPosition(...args) {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(...args, resolve, reject);
});
}
async function go() {
console.log('starting');
const pos = await getCurrentPosition();
console.log(pos);
console.log('finished');
}
go();
- Allows us to make a callback-oriented API work on promises
- 3rd party options also, including a built-in Node utility
class Dog {
constructor(name, breed) {
this.name = name;
this.breed = breed;
}
barks = 0;
bark() {
console.log(`Bark Bark! My name is ${this.name}`);
this.barks = this.barks + 1;
}
}
- Declaring barks outside constructor is an example of a class property
- Commonly used in React
class Mouse extends React.Component {
static propTypes = {
render: PropTypes.func.isRequired
}
state = { x: 0, y: 0 }
...
}
https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce
Needs this Babel transform https://babeljs.io/docs/plugins/transform-class-properties/
The padStart() method pads the current string with another string (repeated, if needed) so that the resulting string reaches the given length.
"1".padStart(3, 0) // 001
- Useful if trying to right align text to the longest string in a series
const strings = ['short', 'medium size', 'this is really really long', 'this is really really really really really really long'];
const longestString = strings.sort(str => str.length).map(str => str.length)[0];
strings.forEach(str => console.log(str.padStart(longestString)));
Array.includes()
['a', 'b', 'c'].includes('c') //true
Exponential Operator
3 ** 3 //27
const names = [
'wes',
'kait',
'lux',
'poppy',
];
const people = {
wes: 'Cool',
kait: 'Even Cooler!',
lux: 'Coolest',
poppy: 'Smallest',
snickers: 'Bow wow',
}
function family(
mom,
dad,
children,
dogs,
) {
}
- Just as with arrays and objects, function arguments can accept trailing commas
- Rationale is that someone contributing will not have a line they're not really associated with come up as their edit just because they added a comma
- Typically best to just add rule to ESLint and Prettier
const inventory = {
backpacks: 10,
jeans: 23,
hoodies: 4,
shoes: 11
};
// Make a nav for the inventory
const nav = Object.keys(inventory).map(item => `<li>${item}</li>`).join('');
console.log(nav);
// tell us how many values we have
const totalInventory = Object.values(inventory).reduce((a, b) => a + b);
console.log(totalInventory);
// Print an inventory list with numbers
Object.entries(inventory).forEach(([key, val]) => {
console.log(key, val);
});
for (const [key, val] of Object.entries(inventory)) {
console.log(key);
if (key === 'jeans') break;
}
- Object.values() is like the other side of Object.keys()
- Object.entries() returns each point in the object as a two-value array
- Benefit of for...of is the ability to break out of it, which cannot be done in map or forEach