So:
/ project_root
/ app
index.js
/ node_modules
...
.babelrc
.gitignore
index.js
package.json
{
"presets": ["es2015", "stage-0"]
}
'use strict';
/*
* Only our entry point needs to use the 'require' pattern
* Everything after babel-register will be able to use ES6!
*/
require("babel-register");
require('babel-polyfill');
require('./app');
'use strict';
console.log('Hello ES6!');
// we've declared foo in the global scope - all functions in your program have access
var foo = 'foo';
function bar () {
var myBar = 'my bar';
console.log(foo) // foo
// this is fine, because foo is declared in the function bar's parent scope
}
console.log(foo) // foo
console.log(myBar) // ReferenceError!
function fooWithVar (bar) {
if (bar) {
var baz = 'baz';
}
console.log(baz);
}
foo('baz') // baz
function fooWithLet (bar) {
if (bar) {
let baz = 'baz';
}
console.log(baz);
}
fooWithVar('baz'); // 'baz'
fooWithLet('baz'); // ReferenceError: baz is not defined
// index.js
// we'll use the core readline library to write our CLI
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function main () {
console.log("Welcome to ConcertGoer!");
rl.question('Ready to learn ES6? ', function (answer) {
console.log('You said: ', answer);
rl.close();
});
}
// invoke main to kick off our app
main();
// say we have an object called Neo...
const Neo = {};
// and we also have a function
const bendSpoon = function () {
console.log(this.spoon);
};
// let's give our object a property and a method
Neo.spoon = 'spoon';
Neo.bendSpoon = bendSpoon;
// the execution context of bendSpoon is Neo, so this refers to Neo
Neo.bendSpoon(); // spoon
// the execution context of bendSpoon is the global context - there is no spoon
bendSpoon(); // undefined
const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];
Neo.kick = function (agent) {
console.log('Neo kicked ', agent);
};
Neo.kickAgentSmiths = function () {
agentSmiths.forEach(function (agent) {
this.kick(agent);
});
};
Neo.kickAgentSmiths(); // TypeError: this.kick is not a function
// How can we help Neo?
/* if you have one argument and don't use brackets
you can just write a snappy one-liner
This is called the 'concise body'
*/
arr.map(i => i * 2);
// multiple arguments go in parentheses
arr.reduce((prev, next) => prev + next);
// no arguments are just an empty ()
// note that you can use this for any function expression, not just callbacks!
let foo = () => console.log('bar');
/* if you need more than one line, then you need to use brackets
and can't omit the return statement
This is called the 'block body'
*/
let baz = (x) => {
if (x > 1) return x;
else return 1;
}
const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];
Neo.kick = function (agent) {
console.log('Neo kicked ', agent);
};
Neo.kickAgentSmiths = function () {
agentSmiths.forEach(agent => this.kick(agent));
};
Neo.kickAgentSmiths();
/*
Neo kicked Smith1
Neo kicked Smith2
Neo kicked Smith3
*/
const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];
Neo.kick = function (agent) {
console.log('Neo kicked ', agent);
};
Neo.kickAgentSmiths = () => {
agentSmiths.forEach(agent => this.kick(agent));
};
Neo.kickAgentSmiths(); // TypeError: the Matrix has you
// index.js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function main () {
rl.question('What would you like to do > ', command => {
switch (command) {
case 'search':
// we'll fill this in next...
break;
default:
console.log('Not a valid command');
main();
}
});
}
// index.js
const http = require('http'); // let's pull in http to talk to Bandisintown
// ....
case 'search':
rl.question('Enter an artist to search for > ', bandName => {
let responseBody = '';
http.get({
host: 'api.bandsintown.com',
// at the end there, where it says ES6_TEST - make up something of your own!
path: '/artists/' + bandName.replace(/\s/g, '%20') + '/events.json?app_id=ES6_TEST'
}, res => {
res.on('data', data => responseBody += data);
res.on('end', () => {
console.log(JSON.parse(responseBody));
main();
});
}) // CAREFUL! no semicolon here
.on('error', error => console.error(error));
});
// ...
// index.js
// ....
case 'search':
rl.question('Enter an artist to search for > ', function (bandName) {
let responseBody = '';
http.get({
host: 'api.bandsintown.com',
path: '/artists/' + bandName.replace(/\s/g, '%20') + '/events.json?app_id=ES6_TEST'
}, res => {
res.on('data', function (data) {
responseBody += data;
});
res.on('end', function () {
console.log(JSON.parse(responseBody));
main();
});
})
.on('error', error => console.error(error))
});
// ...
console.log(`
Hello
World!
`);
/*
Hello
World
*/
// Returns and spaces/tabs are included in the console output!
function stayClassy (city) {
console.log(`Stay classy, ${city}`);
}
stayClassy('San Diego'); // => Stay classy, San Diego
// index.js
// ....
case 'search':
rl.question('Enter an artist to search for > ', bandName => {
let responseBody = '';
http.get({
host: 'api.bandsintown.com',
path: `/artists/${bandName.replace(/\s/g, '%20')}/events.json?app_id=ES6_TEST`
}, res => {
res.on('data', data => responseBody += data);
res.on('end', () => {
let events = JSON.parse(responseBody);
console.log(`${bandName} has ${events.length} concerts coming up`);
main();
});
}) // CAREFUL! no semicolon here
.on('error', error => console.error(error));
});
// ...
/* export.js */
// you can export multiple values
export const foo = 'foo';
export function bar () {
console.log('bar');
}
// you can also specify a default export
export default function baz () {
console.log('baz');
}
/* import.js */
// we can import default values by name
import baz from './export';
// we can specify which exports to import using brackets
import {foo, bar} from './export';
// we can also import all by assigning a namespace
import * as foobar from './export';
/* index.js */
import * as readline from 'readline';
import search from './search';
// we'll create a new file, search.js, in our app folder
/* search.js */
import * as http from 'http';
export default function search (rl, done) {
rl.question('Enter an artist to search for > ', bandName => {
let responseBody = '';
http.get({
host: 'api.bandsintown.com',
path: `/artists/${bandName.replace(/\s/g, '%20')}/events.json?app_id=ES6_TEST`
}, res => {
res.on('data', data => responseBody += data);
res.on('end', () => {
let events = JSON.parse(responseBody);
console.log(`${bandName} has ${events.length} concerts coming up`);
done();
});
})
.on('error', error => {
console.error(error);
done();
});
});
}
/* index.js */
import * as readline from 'readline';
import search from './search';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log("Welcome to ConcertGoer!");
function main () {
rl.question('What would you like to do > ', command => {
switch (command) {
case 'search':
search(rl, main);
break;
default:
console.log('Not a valid command');
main();
}
});
}
main();
fs.readFile('file.txt', 'utf8', function (err, data) {
if (err) throw err;
console.log(data);
});
/* index.js */
// we head on down the main thread
console.log('a')
/*
fs.readFile is always ASYNC
*/
fs.readFile('file.txt', 'utf8', function (err, data) {
if (err) throw err;
console.log(data)
});
// back on the main thread...
console.log('c');
/*
When this logs to the console, we'll see
'a'
'c'
'I am the contents of file.txt!'
*/
foo(function (err1, data) {
if (err1) throw err1;
bar(data, function (err2, moarData) {
if (err2) throw err2;
baz(moarData, function (err3, evenMoarData) {
if (err3) throw err3;
killMeNow(/* ... */)
});
});
});
ourPromise
.then(successHandlingFunction, errorHandlingFunction)
// Let's say that fetch does something async, like make an http request to a certain api
// Instead of using a callback, fetch will return a Promise!
// With this http request, let's say we want an array of events, just like the one in our application
let promiseForEvents = fetch('http://api/artists/events')
promiseForEvents
.then(
function (events) {
// if the request is successful, the success handler will get us the events
console.log(events);
},
function (error) {
// if the request errors, the error handler will log an error
console.log(error)
});
ourPromise
.catch(errorHandlingFunction)
let promiseForEvents = fetch('http://api/artists/events')
promiseForEvents
.then(function (events) {
// if the request is successful, calling .then will get us the events
console.log(events);
})
.catch(function (error) {
// if the request is unsuccessful, the error will be caught by the .catch
console.log(error)
});
// we can simplify this syntax further
fetch('http://api/artists/events')
.then(events => console.log(events)) // watch out! no semicolon, we're chaining!
.catch(error => console.error(error));
// chaining promises
fetch('http://artist_api/artist/kanye')
.then(kanyeInfo => {
// we use the info we got back to make another asynchronous call
// fetch returns another promise, so we can keep using .then!
return fetch(`http://other_api/events_by_artist/${kanyeInfo.id}`)
})
.then(kanyeEvents => console.log("Here are Kanye's events: ", kanyeEvents)
.catch(error => console.error(error));
// If either of those calls err'd, we would catch it here! Wow!
function prompt (ques) {
return new Promise(function (resolve, reject) {
rl.question(ques, function(command) {
if (!command) reject('No command given');
resolve(command);
});
});
}
// we can also use arrow functions!
function prompt (ques) {
return new Promise((resolve, reject) => {
rl.question(ques, command => {
if (!command) reject('No command given');
resolve(command);
});
});
}
// now, here's how we use our new prompt
prompt('What would you like to do > ')
.then(command => {
switch (command) {
// etc...
}
})
.catch(err => console.error(err));
/* Create a new file in /app called prompt.js */
'use strict';
import * as readline from 'readline'; // now, our prompt file will control readline
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
export default function prompt (ques) {
return new Promise((resolve, reject) => {
rl.question(ques, answer => {
// ternary control operators are just shorthand for if...else
answer ? resolve(answer) : reject('No command given');
});
});
}
/* index.js */
'use strict';
import prompt from './prompt'; // import prompt instead!
import search from './search';
console.log("Welcome to ConcertGoer!");
function main () {
prompt('What would you like to do > ')
.then(command => {
switch (command) {
case 'search':
search(main); // we no longer have to pass rl!
break;
default:
console.log('Not a valid command');
main();
}
})
.catch(error => {
console.error(error);
main();
});
}
main();
/* create a new file in /app called fetch.js */
'use strict';
import * as http from 'http';
export default function fetch (bandName) {
return new Promise((resolve, reject) => {
let responseBody = '';
http.get({
host: 'api.bandsintown.com',
path: `/artists/${bandName.replace(/\s/g, '%20')}/events.json?app_id=ES6_TEST`
}, res => {
res.on('data', data => responseBody += data);
res.on('end', () => {
resolve(JSON.parse(responseBody)); // resolve our JSON!
});
res.on('error', error => reject(error)); // reject our error
});
});
}
/* search.js */
'use strict';
import fetch from './fetch';
import prompt from './prompt'; // new imports!
export default function search (done) { // no more rl!
let _bandName; // we'll cache bandName here
prompt('Enter an artist to search for > ')
.then(bandName => {
_bandName = bandName;
return fetch(bandName);
})
.then(events => {
console.log(`${_bandName} has ${events.length} concerts coming up`);
done();
})
.catch(error => {
console.error(error);
done();
});
};
// This is SO much more readable than before!
let array = [1, 2, 3];
let iteratorForArray = {};
iteratorForArray.next()
// {value: 1, done: false}
iteratorForArray.next()
// {value: 2, done: false}
iteratorForArray.next()
// {value: 3, done: false}
iteratorForArray.next()
// {value: undefined, done: true}
'use strict';
// an array is a built-in iterable
let numberArray = [1, 2, 3];
// Symbol.iterator is the special property that contains a function that returns an iterator
let it = numberArray[Symbol.iterator]();
// we can then use the iterator to manually iterate through the array's values
it.next(); // Object { value: 1, done: false }
it.next(); // Object { value: 2, done: false }
it.next(); // Object { value: 3, done: false }
it.next(); // // Object { value: undefined, done: true }
let stringArray = ['a', 'b', 'c'];
// the for...of loop consumes an iterable
for (let ch of stringArray) {
console.log(ch) // a b c
}
let strArray = ['a', 'b', 'c'];
strArray[Symbol.iterator] = function () {
let idx = 0,
collection = this,
len = this.length;
return {
next: function () {
if (idx < len) {
let value = collection[idx];
idx++;
return {
value: value + '!',
done: false
};
} else {
return {
value: undefined,
done: true
};
}
}
};
};
for (let ch of strArray) {
console.log(ch); // a! b! c!
}
/* search.js */
.then(events => {
events = events.filter(event => event.venue.city === 'New York');
console.log(`\nUpcoming concerts for ${_bandName} in New York`);
for (let event of events) {
console.log(`
${event.artists[0].name}: ${new Date(event.datetime)}
${event.venue.name} ${event.venue.city}, ${event.venue.region}
`);
}
done();
})
/*
A bit anticlimactic? Don't worry - we'll use iterators more in a bit!
*/
// we declare a generator using an asterisk
function* myGenerator (n) {
// we yield values with the yield keyword
yield n;
n++;
// we can yield as many times as we want
// each yield is a "pause" in the generator function's execution
yield n;
}
/*
invoking a generator function returns an iterator!
we've passed in a value for the parameter "n", but
no other code in the generator has executed yet
*/
let myIterator = myGenerator(41);
// each call to next advances us to the next yield
let firstYield = myIterator.next();
console.log(firstYield.value); // 41
let secondYield = myIterator.next();
console.log(secondYield.value); //42
// because we can pause generators, it doesn't matter if they run forever
function* generateIndex (initialValue) {
let x = initialValue;
while (true) x = yield x;
/*
Something really cool is happening here!
We can yield on the right side of an assignment.
When we call .next on the iterator, we can pass in a value as a parameter.
That value will then be the value that gets assigned!
*/
}
let i = 0,
it = generateIndex(i);
for (; i < 10; i++) console.log(it.next(i).value);
// 0 1 2 3 4 5 6 7 8 9
/*
Each yield pauses the function and yields a value.
If the yield is in an assignment, it will also wait
to receive a value from outside as well!
*/
/*
What if we have an array of api requests, and we only want
to get the contents of a request that will give us more than
twenty items?
*/
function* generateAsyncRequests (arrayOfRequests) {
for (let request of arrayOfRequests) {
yield fetch(request); // fetch, like our fetch, requests an http resource
}
}
function cycle (iterator) {
let request = iterator.next(); // yields object with promise for the data
if (request.done) return console.log('No requests yielded more than twenty items');
else return request.value
.then(data => {
if (data.length > 20) return data;
else cycle(iterator);
});
}
let urls = ['/api/techno', '/api/electronica', '/api/dance'],
it = generateAsyncRequests(urls);
cycle(it);
/* prompt.js */
/*
we're going to need both the answer and the event to resolve from our prompt
so let's spin up a different version of our prompt function
*/
export function promptForEvent (event, ques) {
return new Promise((resolve, reject) => {
rl.question(ques, answer => answer ?
resolve({ answer: answer, event: event }) : reject('No command given'));
});
}
/* search.js */
import fetch from './fetch';
import prompt, {promptForEvent} from './prompt'; // import our new prompt!
// our generator function
function* generateEvents (events) {
for (let event of events) {
yield promptForEvent(event, `
${event.id}: ${new Date(event.datetime)}
${event.venue.name} ${event.venue.city}, ${event.venue.region}
Do you want to attend this event? > `);
}
}
/* search.js */
// our function for "cycling" through the events
function cycleEvents (it, done) {
let result = it.next();
if (result.done) done();
else result.value
.then(response => {
if (response.answer === 'y') console.log(`
Added the show at ${response.event.venue.name} to the schedule!
`);
cycleEvents(it, done);
})
.catch(err => {
console.error(err);
cycleEvents(it, done);
});
}
/* search.js */
// our search function is now a lot cleaner!
export default function search (done) {
let _bandName;
prompt('Enter an artist to search for > ')
.then(bandName => {
_bandName = bandName;
return fetch(bandName);
})
.then(events => {
events = events.filter(event => event.venue.city === 'New York');
console.log(`\nUpcoming concerts for ${_bandName} in New York`);
let it = generateEvents(events);
cycleEvents(it, done);
})
.catch(error => {
console.error(error);
done();
});
};
class Dog {
// the constructor function itself goes in a named function called 'constructor'
constructor (name, age, breed) {
this.name = name;
this.age = age;
this.breed = breed;
}
// no need to attach methods to the prototype, like Dog.prototype.bar = function () {}!
bark () {
console.log('Arf!');
}
}
let snoopy = new Dog("Snoopy", 5, "Beagle");
snoopy.bark(); // 'Arf!'
// our parent class
class Game {
constructor (title, numPlayers, timeToPlay) {
this.title = title;
this.numPlayers = numPlayers;
this.timeToPlay = timeToPlay;
}
play () {
console.log("Let's play " + this.title + '!');
}
}
// using 'extends' removes the need to mess with accessing the function prototype
class Avalon extends Game {
// using super is easier than using Game.call()
constructor (title, numPlayers, timeToPlay, rules) {
super(title, numPlayers, timeToPlay);
this.rules = rules;
}
play () {
super.play(); // easily access any methods from the base class using super
console.log("I'm not Merlin!");
}
}
class Minion extends Characters {
constructor (picture, loyalty) {
super (picture, loyalty);
}
static defaultMinion () {
return new Minion ('minion_1.png', 'evil');
}
}
let defaultMinion = Minion.defaultMinion();
let customMinion = new Minion('minion_2.png, 'also evil');
/* schedule.js */
// create a new file called schedule.js
'use strict';
// we'll use the Node "fs" (filesystem) library to read and write files!
import * as fs from 'fs';
export default class Schedule {
constructor (events) {
this.schedule = events || [];
}
add (event) {
this.schedule.push(event);
console.log('Added to schedule!');
}
/* schedule.js */
import * as fs from 'fs';
export default class Schedule {
// niiiiice!
constructor (events=[]) {
this.schedule = events;
}
add (event) {
this.schedule.push(event);
console.log('Added to schedule!');
}
/* schedule.js */
import * as fs from 'fs';
export default class Schedule {
/* ... */
log () {
if (!this.schedule.length) return console.log('\nNo events currently scheduled\n');
console.log('Your schedule: \n');
for (let event of this.schedule) {
console.log(`
${event.artists[0].name}: ${new Date(event.datetime)}
${event.venue.name} ${event.venue.city}, ${event.venue.region}
`);
}
}
}
// In node, we use fs.readFile and fs.writeFile to read and write files
// the ./ syntax means relative to the file where the node process is executing
fs.readFile('./some-file.json', 'utf8', function (err, data) {
if (error) throw error;
return JSON.parse(data);
});
fs.writeFile('./some-file.json', JSON.stringify(ourData), 'utf8', function (error) {
if (error) throw error;
console.log('Wrote to file successfully!');
});
/*
These callback-version functions are okay. But our Schedule will be a lot cooler
if it returns Promises!
*/
/* schedule.js */
// instances of our schedule will save themselves
save () {
return new Promise ((resolve, reject) => {
fs.writeFile('./schedule.json', JSON.stringify(this.schedule), 'utf8', error => {
if (error) reject(error);
resolve(this);
});
});
}
/* schedule.js */
/*
When we load data for our schedule, we won't have an instance of a schedule yet!
Let's make this a static on the Schedule class, that it can use to create an
instance of itself!
*/
static load () {
return new Promise((resolve, reject) => {
fs.readFile('./schedule.json', 'utf8', (error, data) => {
if (error) reject(error);
data ? resolve(new Schedule(JSON.parse(data))) : resolve(new Schedule());
});
});
}
/* index.js */
/*
We only want to load the schedule on startup, so it's not quite right for our main function.
Let's put all the application start logic in a new function called "bootstrap"
*/
import prompt from './prompt';
import search from './search';
import Schedule from './schedule';
function bootstrap () {
console.log("Welcome to ConcertGoer!");
Schedule.load()
.then(schedule => {
schedule.log();
main(schedule);
})
.catch(error => {
console.log('No schedule.json found - one will be created upon adding an event!');
main(new Schedule());
});
}
/* index.js */
function main (schedule) {
prompt('What would you like to do > ')
.then(command => {
switch (command) {
case 'search':
search(schedule, main); // let's pass our schedule into search so we can use it there
break;
case 'view schedule': // let's give ourselves a new commend
schedule.log();
main(schedule);
break;
default:
console.log('Not a valid command');
main(schedule); // main takes a schedule now - be sure to pass it down
}
})
.catch(err => {
console.log(err);
main(schedule); // same here!
});
}
// kick things off with our bootstrap!
bootstrap();
/* search.js */
// account for our new schedule
export default function search (schedule, done) {
let _bandName;
prompt('Enter an artist to search for > ')
.then(bandName => {
_bandName = bandName;
return fetch(bandName);
})
.then(events => {
events = events.filter(event => event.venue.city === 'New York');
console.log(`\nUpcoming concerts for ${_bandName} in New York`);
let it = generateEvents(events);
cycleEvents(it, schedule, done); // we'll pass the schedule along here
})
.catch(error => {
console.error(error);
done(schedule); // don't forget here too!
});
}
/* search.js */
// pass in our new schedule!
function cycleEvents (it, schedule, done) {
let result = it.next();
// save our schedule when we're done
if (result.done) schedule.save()
.then(savedSchedule => done(savedSchedule))
.catch(console.error);
// add to our schedule if we say "y" at the prompt!
else result.value
.then(response => {
if (response.answer === 'y') schedule.add(response.event);
cycleEvents(it, schedule, done);
})
.catch(err => {
console.error(err);
cycleEvents(it, schedule, done);
});
}
/* index.js */
function main (schedule) {
prompt('What would you like to do > ')
.then(command => {
switch (command) {
case 'search':
search(schedule, main);
break;
case 'view schedule':
schedule.log();
main(schedule);
break;
// all good things must come to end...
case 'exit':
console.log('Goodbye!');
process.exit();
break;
Want to keep hacking?
Try making it so you can remove events from the schedule!
Try making it so that the user can choose which city to search!
Try making it so that events fall off the schedule once their date passes!
Thanks for coming!