Created
June 20, 2018 21:24
-
-
Save slaughtr/9e4356ef2ce900236c192adf878edb21 to your computer and use it in GitHub Desktop.
es6
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I cannot absolutely guarantee I got everything completely correct, but I'm 95% sure I got at least 95% of it correct | |
// ----------------- | |
// --- PROMISES | |
// ----------------- | |
// Callback hell: | |
doSomething(params, (err, data) => { | |
if (err) console.error(err) | |
else { | |
somethingElse(params, (err, data) => { | |
// you can see where this is going | |
// and good luck with error handling | |
}) | |
} | |
}) | |
// Promises | |
const doSomething = params => { | |
return new Proimse((resolve, reject) => { | |
const data = process(params) | |
resolve(data) // | |
}) | |
} | |
doSomething(params) | |
.then(data => somethingElse) | |
.catch(err => console.error) | |
// Parallel, you say? | |
const arr = ['lots', 'of', 'items', ...] | |
Promise.all(arr.map(doSomething)).then(resultArr => somethingElse) | |
// ----------------- | |
// --- LET & CONST | |
// ----------------- | |
// Bad syntax on purpose, I was avoiding using let/var/const in function declarations | |
// var | |
function() { | |
var inTheGlobalSpace = 'No matter where you put it' | |
var inTheGlobalSpace = `So you can't re-use the variable name safely` | |
} | |
var inTheGlobalSpace = 'Now everything might break' | |
// let | |
function() { | |
let thisExistHere = 'And not outside the current context' | |
let thisExistHere = 'You cannot re-declare me.' // SyntaxError: Identifier 'thisExistHere' has already been declared | |
thisExistHere = 'But you can reassign me!' | |
} | |
console.log(thisExistsHere) // ReferenceError: thisExistsHere is not defined | |
// const | |
const notReassignable = 'Once set, you cannot re-assign this variable' | |
notReassignable = 'This will not work' // TypeError: Assignment to a constant variable | |
// ----------------- | |
// --- ARROW FUNCTIONS | |
// ----------------- | |
// The old way | |
function operator() { | |
return `Did you know it's called the function operator?` | |
} | |
// Younger, stronger, smarter | |
operator = () => 'Empty parenthesis with no arguments.' | |
// Not convinced? Check this out | |
// Bloated, busy, gross | |
const iChooseYou = team.map(function(pokémon) { | |
return pokémon.name | |
}).filter(function(name) { | |
return name === 'Charizard' | |
}) | |
// Clean, readable, slick | |
const iChooseYou = team.map(pokémon => pokémon.name).filter(name => name === 'Charizard') | |
// Easy return of objects, without fear of ASI ruining your day | |
const decorate = team.map(pokémon => ({ | |
oldName: pokémon.name, | |
newName: pokémon.name.toUpperCase(), | |
status: 'AWESOME' | |
})) | |
// The scope of 'this' is lexically bound with arrow functions. They do not have their own 'this', and use the 'this' of their immediate scope | |
// ----------------- | |
// --- FOR...OF | |
// ----------------- | |
// Just the best | |
const starTrek = [ 'TOS', 'TNG', 'VOY', 'DS9', 'ENT', 'DSC'] | |
// Better than a for loop, but... | |
Promise.all(starTrek.map(series => { | |
return new Promise(resolve => { | |
myDatabase.update({Table: 'best_shows', Item: series}).then(resolve) | |
}) | |
})) | |
// We can make it better (another one in async/await) | |
for (const series of starTrek) { | |
myDatabase.update({Table: 'best_shows', Item: series}).then(() => null) | |
} | |
// Utilize break (another one in async/await) | |
for (const series of starTrek) { | |
netflix.search({Show: series}).then(hasIt => { | |
if (hasIt === true) { | |
startNetflixShow(series) | |
break; | |
} | |
}) | |
} | |
// Without break | |
Promise.all(starTrek.map(series => { | |
return new Promise(resolve => { | |
netflix.search({Show: series}).then(hasIt => { | |
if (hasIt === true) resolve(series) | |
else resolve(false) | |
}) | |
}) | |
})).then(resultArray => startNetflixShow(resultArray.find(item => item))) // item will be truthy if resolve(series) occurred | |
// Works wonderfully with objects | |
const captains = { | |
TOS: 'Kirk', | |
TNG: 'Picard', | |
VOY: 'Janeway', | |
DS9: 'Sisko', | |
ENT: 'Archer', | |
DSC: 'Pike', | |
WeWereRobbed: 'Worf' | |
}; | |
for (const show of Object.keys(captains)) { | |
console.log(`${captains[show]} was the captain of ${show}`); | |
// Kirk was the captain of TOS, Picard was the captain of TNG, Janeway was the... | |
} | |
// for...in has caveats | |
// maybe a library somewhere in your dependencies did this: | |
Object.prototype.messUpYourCode = 'Some thing'; | |
for (const show in captains) { | |
console.log(`${captains[show]} was the captain of ${show}`); | |
// ..., Worf was the captain of WeWereRobbed, Some thing was the captain of messUpYourCode | |
} | |
// However, this can be avoided by adding if (captains.hasOwnProperty(show)) {} around everything | |
for (const show in captains) { | |
if (captains.hasOwnProperty(show)) { | |
console.log(`${captains[show]} was the captain of ${show}`); | |
// ..., Pike was the captain of DSC, Worf was the captain of WeWereRobbed | |
} | |
} | |
// Arrays are not guaranteed to iterate in order with for...in, so be careful if that's important in your code | |
// The variable in a for...in on an array is the index of the item | |
// you CAN do single-line for...in and for...of statements | |
for (const index in ['a','b','c']) console.log(index) // 0, 1, 2 | |
// ----------------- | |
// --- DESTRUCTURING, REST, AND SPREAD | |
// ----------------- | |
// Object desctructuring is great for passing data around | |
// real-world example! | |
exports.handler = async function(event, context, callback) { | |
// get params from API call | |
const { client, project, folder, user, batch, test, url } = event.query; | |
if (client && project && sub && user && batch && test && url) { | |
logger.debug(`Params: client = ${client} | project = ${project} | sub = ${sub} | user = ${user} | batch = ${batch} | test = ${test} | url = ${url}`); | |
const lambdaParams = { | |
FunctionName: 'acknowledgeAlert', | |
// Event should allow async execution (the api call will return ~immediately) | |
InvocationType: 'Event', | |
// we don't want the logs in this function | |
LogType: 'None', | |
// it's looking for userId, clientId, batchId, project, sub, testId, url | |
Payload: JSON.stringify({ | |
clientId: client, | |
project: project, | |
folder: folder, | |
userId: user, | |
testId: test, | |
url: url, | |
batchId: batch | |
}) | |
}; | |
try { | |
await lambda.invoke(lambdaParams).promise(); | |
logger.info('Invoked lambda'); | |
context.succeed(); | |
} catch (error) { | |
logger.error(error); | |
context.fail(); | |
} | |
} else { | |
logger.error('Bad params'); | |
context.fail(); | |
} | |
}; | |
// Imagine we get these results from an API call | |
// { | |
// 's01e01': 'Drunk people', | |
// 's01e02': 'Angry drunk people', | |
// ... | |
// 's68e37': 'Too drunk to be angry', | |
// 's69e38': 'This is not a good joke' | |
// ... | |
// } | |
// Destructuring assignment | |
const { s01e01, s01e02 } = result | |
console.log(s01e01, s01e02) // Drunk people Angry drunk people | |
// Rest operator | |
const { s01e01, s01e02, ...rest } = result | |
console.log(rest) // { s01e03: 'Another one',...,s68e37: 'Too drunk to be angry',...etc} | |
// Also works with arrays | |
const [ first, second, ...rest ] = [1,2,3,4,5,6,7] // rest = [3,4,5,6,7] | |
// And the opposite of rest, the Spread operator | |
// Concat arrays | |
const moreNumbers = [ ...[1,2,3,4,5,6], ...rest] // [1,2,3,4,5,6,3,4,5,6,7] | |
// Clone arrays | |
const cloneWithoutModifyingOriginal = [...moreNumbers] | |
// Clone objects | |
const mutableVariable = {...dog, ...cat} | |
// Renaming properties | |
const obj = { animal: { dog: `Seymour` }, place: `Too far away` } | |
// We can't help but call dogs puppers, right? | |
const { animal: { dog: pupper }, place: where } = obj // (can be confusing syntax here) | |
// Replaces | |
const pupper = obj.animal.dog // ‘Seymour` | |
const where = obj.place // ‘Too far away` | |
// ----------------- | |
// --- SETS | |
// ----------------- | |
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set | |
// De-duplicating an array | |
// The standard way, usually. Nothing wrong with it, it's short enough. | |
// returns true for only the first instance of item | |
const unique = duplicates.filter((item, index) => duplicates.indexOf(item) === index) | |
// With Sets | |
const unique = [...new Set(duplicates)] | |
// Note this works because of the spread operator, as Sets are not Arrays, but they are iterables | |
// or, with Array.from() | |
const unique = Array.from(duplicates) // not as fun :( | |
// Practical use | |
const dreamJobs = new Set() | |
emitter.on('upload', item => dreamJobs.add(item)) | |
emitter.emit('upload', 'Tyrannosaurus Rex') // dreamJobs = Set ['Tyrannosaurus Rex'] | |
emitter.emit('upload', 'Tyrannosaurus Rex') // nothing added, dreamJobs = Set ['Tyrannosaurus Rex'] | |
emitter.emit('upload', 'Brontosaurus') // dreamJobs = Set ['Tyrannosaurus Rex', 'Brontosaurus'] | |
await processItems(dreamJobs) | |
dreamJobs.clear() // dreamJobs.size = 0 | |
emitter.emit('upload', 'Tyrannosaurus Rex') // dreamJobs = Set ['Tyrannosaurus Rex'] | |
// Iteration with sets | |
// Set().forEach() , or for...of by using Set.keys() or Set.values() - both do the same thing...as does Set.entries(), though it returns each value as [val, val] | |
const dreamJobs = new Set(['Tyrannosaurus Rex', 'Brontosaurus']) | |
dreamJobs.forEach(job => console.log) // 'Tyrannosaurus Rex', 'Brontosaurus' | |
for (const job in dreamJobs.values() /* or dreamJobs.keys() */) console.log(job) // 'Tyrannosaurus Rex', 'Brontosaurus' | |
for (const job in dreamJobs.entries() ) console.log(job) // ['Tyrannosaurus Rex', 'Tyrannosaurus Rex',], ['Brontosaurus', 'Brontosaurus'] | |
// destructuring assignment with array | |
for (const [a, b] in dreamJobs.entries() ) console.log(a) // 'Tyrannosaurus Rex', 'Brontosaurus' | |
// Sets use different methods than arrays: Set.size(), Set.has(value), Set.add(value), Set.delete(value), and Set.clear() | |
dreamJobs.size() // 2 | |
dreamJobs.has('Tyrannosaurus Rex') // true | |
dreamJobs.has('Shark') // false | |
dreamJobs.add('Shark') // dreamJobs = Set ['Tyrannosaurus Rex', 'Brontosaurus', 'Shark'] | |
dreamJobs.delete('Shark') // dreamJobs = Set ['Tyrannosaurus Rex', 'Brontosaurus'] | |
dreamJobs.clear() // dreamJobs = Set {} | |
dreamJobs.size() // 0 | |
// ----------------- | |
// --- MODULES | |
// ----------------- | |
// You can use require() statements inline | |
const rarelyCalledFunction = params => { | |
require('fs').writeFileSync('./run-without-error.json', params) | |
} | |
// You can also call functions using require() and an IIFE (immediately invoked function expression) | |
// say-hi.js | |
console.log('Hi Mom') | |
// index.js | |
(() => require('./say-hi.js'))() // console logs 'Hi Mom' | |
// You can use destructuring with imports | |
// An entire library imported...for one module | |
import * as React from 'React' | |
React.Component.doStuff() | |
// Destructuring, only the component you need | |
import { Component } from 'React' | |
Component.doStuff() | |
// Or rename it | |
import { Component as RC } from 'React' | |
RC.doStuff() | |
// Sharing resources can be very helpful but can also lead to issues | |
// In Node, all files at the same directory level/same module share instances | |
// Especially useful for database connections, as they take ~20x longer to start than the rest of your code | |
// ./app/db.js | |
module.exports.writeToTable = db.client.write(msg) | |
// ./app/donger.js | |
const { writeToTable } = require('db') | |
writeToTable('ლ(́◉◞౪◟◉‵ლ)') | |
// ./app/emoji.js | |
const { writeToTable } = require('db') // same instance of db.js so the client will still be loaded | |
writeToTable('🤷') | |
// With Import, it depends on whether a class or an instance is exported | |
// Singleton, so shared instance | |
class Settings {} | |
export let singleton = new Settings() | |
// Not instantiated, so can't be singleton | |
export class Settings {} | |
// Other files have to instantiate with new | |
const settings = new Settings() | |
// ----------------- | |
// --- ETC | |
// ----------------- | |
// Arrays | |
const setToArray = Array.from(someSet) // Creates a clone | |
const unicode = Array.from('▲ESTHETIC') // ['▲','E','S','T','H','E','T','I','C'] | |
// Array.find() - Returns the first item that meets a criteria | |
const seven = [1,3,7,9,11,13,17,21].find(num => num % 7 === 0) // 7 | |
// Strings | |
// String.includes(), startsWith(), endsWith() | |
const carGoes = 'VROOM' | |
console.log(carGoes.includes('OO')) // true | |
console.log(carGoes.startsWith('VR')) // true | |
console.log(carGoes.endsWith('M')) // true | |
// Numbers | |
// JavaScript is infamous for mishandling floats when doing comparisons | |
// Number.EPSILON - allows comparing floats | |
const lolFloats = Math.abs(0.2 - 0.3 + 0.1); // you'd think 0 | |
console.log(lolFloats === 0) // false | |
console.log(lolFloats); // expected output: 2.7755575615628914e-17 aka 0.000000000000000027755575615628914 | |
console.log(lolFloats < Number.EPSILON); // true | |
// Number.isNaN() - actually does what you’d think | |
isNaN('123ABC'); // true: parseInt("123ABC") is 123 but Number("123ABC") is NaN | |
Number.isNaN('123ABC') // false | |
isNaN(''); // false: the empty string is converted to 0 which is not NaN | |
Number.isNaN('') // true | |
// Quick type coercion to Number from string, great for API results | |
const num = '42' | |
console.log(23 + +num) // the + attatched to the variable coerces it into a number | |
console.log(typeof +'12345') // number | |
// Objects | |
// Spread operator isn't always available. Object.assign() uses get() and set() so may be preferable in some situations | |
// Object.assign() – shallow clone | |
const newObj = Object.assign({a: 1, b: 2}, {}) | |
// newObj = {a: 1, b: 2} | |
// Concat objects | |
const newObj = Object.assign({a: 1, b: 2}, {c: 3, d: 4}) | |
// newObj = { a: 1, b: 2, c: 3, d: 4 } | |
// To do a deep clone (clone all properties, no references to original object in memory) | |
const newObj = JSON.parse(JSON.stringify(oldObj)) | |
// Template literals | |
// Template literals are strings that allow inline variables. | |
//They are created with backticks: ` | |
console.log(typeof ``) // string | |
// Variables are used with ${} | |
console.log(`Got status code: ${response.statusCode}, headers: ${response.headers}`) | |
// Multi-line supported | |
let multiLine = `Lorem ipsum dolor sit amet, | |
consectetur adipiscing elit. | |
Maecenas varius tempor arcu, | |
quis hendrerit nunc accumsan quis.` | |
// Default Parameters | |
// Default parameters are used if no value is passed, or the value is undefined | |
// Just your normal rectangle factory | |
const createRectangle = (w, h, color) => ({width: w, height: h, color, area: w*h}) | |
// If we provide all the parameters, no problem! | |
console.log(createRectangle(1, 1, 'red')) // { width: 1, height: 1, color: 'red', area: 1 } | |
// What if x is undefined | |
let x = undefined | |
console.log(createRectangle(1, x, 'green')) // { width: 1, height: undefined, color: 'green', area: NaN } | |
// Default parameters to the rescue | |
const createRectangle = (w = 5, h = 5, color = 'blue') => ({width: w, height: h, color, area: w*h}) | |
// Now x being undefined doesn't break anything | |
console.log(createRectangle(1, x, 'green')) // { width: 1, height: 5, color: 'green', area: 5 } | |
// And we don't have to pass any parameters at all if we don't want to | |
console.log(createRectangle()) // { width: 1, height: 5, color: 'blue', area: 25 } | |
// Default parameters also work with destructuring assignments | |
const obj = { a: 1, b: 'buzz'} | |
// Now we don't get c at all | |
const { a, b, c } = obj // obj = { a: 1, b: 'buzz'} | |
// With default | |
const { a, b, c = true } = obj // obj = { a: 1, b: 'buzz', c: true } | |
// Ternaries & Logical Operators | |
// Shortened if statements, more or less . Format is: condition ? true : false | |
1 > 0 ? console.log('This will print') : console.log('This will not') | |
// Ternaries and || in assignment and params | |
// If all items are falsey, .find() will return undefined, and the default of 'Planet Earth II' will be used instead | |
startNetflixShow(resultArray.find(item => item) || 'Planet Earth II') | |
// Handy when building objects for API calls or database inserts (DynamoDB, for instance, won't accept null values) | |
const obj = { | |
prop: someVar, | |
// if otherVar is falsey (undefined, null, 0, false, '', etc), it will default to 'NOT FOUND' | |
another: otherVar || 'NOT FOUND' | |
} | |
// Ternaries allow slightly more logic, at the expense of readability | |
// If the db was updated within the last hour, data will be what's already in the app object, otherwise get data from the database | |
const data = db.lastUpdated('hours', 'ago') > 1 ? db.getData() : app.data | |
// You can nest ternaries indefinitely, but you shouldn't: | |
const ohDear = (data[0] == '-1') ? 4 : ((data[0] == '0') ? 5 : (data[0] > 0 ? 3 : 2)) | |
// Equivalent if statements. More lines, but much more readable | |
let ohDear | |
if (data[0] === -1) ohDear = 4 | |
else if (data[0] === 0) ohDear = 5 | |
else if (data[0] > 0) ohDear = 3 | |
else ohDear = 2 | |
// ----------------- | |
// --- ASYNC/AWAIT | |
// ----------------- | |
// async/await is syntactic sugar around Promises | |
// async functions ALWAYS return a Promise, await REQUIRES a Promise | |
const getData = async function(id) { | |
return await db.getItem(id) | |
return db.getItem(id) // no await needed, function returns a Promise | |
} | |
const getData = async id => db.getItem(id) // Arrow function implicit return works, too! | |
// await is only available inside async functions | |
const data = await getData(143) // invalid | |
const run = async () => { | |
const data = await getData(143) // valid, since inside async function | |
// do stuff with data | |
} | |
// Since async functions return Promises, they are thenable | |
run().then(result => db.write(result)) | |
// But what about error handling? try/catch to the rescue | |
let data | |
try { | |
data = await getData(143) // database has no entry for 143 | |
} catch (e) { | |
console.error(e) // does not crash | |
data.error = e | |
} | |
// Considerably cleaner code | |
// We can make it better even more! await works in some loops (for, for...of, for...in) without config | |
for (const series of starTrek) { | |
await myDatabase.update({Table: 'best_shows', Item: series}) | |
} | |
// Utilize break (more better with await) | |
for (const series of starTrek) { | |
const hasIt = await netflix.search({Show: series}) | |
if (hasIt === true) { | |
startNetflixShow(series) | |
break; | |
} | |
} | |
// ----------------- | |
// --- es7 | |
// ----------------- | |
// Array.includes() | |
// Returns true/false if item is in array | |
const arr = [1,2,3] | |
console.log(arr.includes(1)) // true | |
console.log(arr.includes(77)) // false | |
// Replaces this | |
console.log(arr.indexOf(1) > -1) // true | |
// Exponentiation Operator - ** | |
// Shorthand for Math.pow() | |
const four = 2**2 | |
console.log(four) // 4 | |
// Replaces this | |
const four = Math.pow(2, 2) | |
console.log(four) // 4 | |
// try/catch bubbling | |
// Errors bubble up to the nearest catch, no further | |
try { | |
try { | |
await writeNoErrors() | |
} catch (err) { | |
console.error(err) // LogicError: There will always be rrors | |
} | |
} catch (error) { | |
console.error(error) // Never called, as the error was caught in the previous catch | |
} | |
// You can force the error to bubble up | |
try { | |
try { | |
await writeNoErrors() | |
} catch (err) { | |
if (err === 'Typo') throw new Error(err) | |
} | |
} catch (error) { | |
console.error(error) // Called with the thrown err | |
} | |
// finally | |
// finally is strange, and in general not what you want | |
try { | |
return 'Try' | |
} catch (err) { | |
return 'Catch' | |
} finally { | |
return 'Finally' // this will ALWAYS be the return value of this try/catch/finally block | |
} | |
// ....maybe more to come |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment