What's New in Modern JavaScript

let versus var

var is quicksand

var cat = 'louis';
var i;

for (i = 1; (i <= 10); i++) {
  var cat = 'loopy cat #' + i;
  console.log(cat);
}
console.log(cat); // cat #10, not louis!
          

let versus var

let: so scope-y fresh

let cat = 'louis';

for (let i = 1; (i <= 10); i++) {
  let cat = 'loopy cat #' + i;
  console.log(cat);
}
 // louis the cat came back!
console.log(cat);
          

const makes you think

Sloppy reuse of variable

class Cat {
  constructor(name) {
    this.name = name;
  }
  pet() {
    console.log(this.name + ' purrs.');
  }
}
          
let cat;

cat = new Cat('Louis');
cat.pet();

cat = new Cat('SpySpy');
// I have to read the previous line
// to know which cat this refers to
cat.pet();

// More than one Louis? Surely you jest!
cat = new Cat('Louis');
          

const makes you think

const: clear & safe

class Cat {
  constructor(name) {
    this.name = name;
  }
  pet() {
    console.log(this.name + ' purrs.');
  }
}
          
const louis = new Cat('Louis');
louis.pet();

const spyspy = new Cat('SpySpy');
// I know which cat this is right away
spyspy.pet();

// ACHTUNG: this will NOT fail
louis.name = 'Some Other Kitty';

// ... But this will fail
louis = new Cat('Louis');
          

for...of for friendly loops

The old way: i am so tired of this

var cats = [ 'Louis', 'SpySpy', 'Pyro' ];

// In the bad old days
var i;
for (i = 0; (i < cats.length); i++) {
  console.log(cats[i]);
}

// IE11: the functional way is not terrible
cats.forEach(function(cat) {
  console.log(cat);
});
          

for...of for friendly loops

The new way: for ... of is friendly and safe

const cats = [ 'Louis', 'SpySpy', 'Pyro' ];

for (const cat of cats) {
  console.log(cat);
}
          

ES6 classes: prototypes without pain

The awkward way

function Cat(name) {
  this.name = name;
}

Cat.prototype.pet = function() {
  console.log(this.name + ' purrs.');
}

const louis = new Cat('Louis');
louis.pet();
          

ES6 classes: prototypes without pain

The ES6 and normal programming languages way

class Cat {
  constructor(name) {
    this.name = name;
  }
  pet() {
    console.log(this.name + ' purrs.');
  }
}

const louis = new Cat('Louis');
louis.pet();
          

Arrow functions: functional programmer candy

The wordy way

const cats = [ 'Louis', 'SpySpy' ];
cats.forEach(function(cat) {
  console.log(`${cat} is a fine cat.`);
});
          

Arrow functions: functional programmer candy

The graceful way

const cats = [ 'Louis', 'SpySpy' ];
cats.forEach(cat => console.log(`${cat} is a fine cat.`));
          

Object literal shorthand: stop typing cat so much

Yo dawg, I heard you liked typing cat

const rateLimit = 2;
let pending = 0;
function api(action, data) {
  // Asynchronous, like a real API call
  return new Promise((resolve, reject) => {
    if (pending >= rateLimit) {
      return reject('RATE LIMITED');
    }
    pending++;
    setTimeout(() => {
      let reversed = '';
      for (let i = (data.cat.length - 1); (i >= 0); i--) {
        reversed += data.cat.charAt(i);
      }
      pending--;
      resolve(reversed);
    }, 500);
  });
}
          
async function go() {
  const cat = 'Louis';
  console.log(await api('reverse', { cat: cat }));
}

go();
          

Object literal shorthand: stop typing cat so much

One cat is nice, and will suffice

const rateLimit = 2;
let pending = 0;
function api(action, data) {
  // Asynchronous, like a real API call
  return new Promise((resolve, reject) => {
    if (pending >= rateLimit) {
      return reject('RATE LIMITED');
    }
    pending++;
    setTimeout(() => {
      let reversed = '';
      for (let i = (data.cat.length - 1); (i >= 0); i--) {
        reversed += data.cat.charAt(i);
      }
      pending--;
      resolve(reversed);
    }, 500);
  });
}
          
async function go() {
  const cat = 'Louis';
  console.log(await api('reverse', { cat }));
}

go();
          

...: give it a rest!

arguments: mmm, crunchy boilerplate

function send(recipient, message /* , message2, message3... */) {
  var messages = Array.prototype.slice.call(arguments, 1);
  console.log(recipient + ', you have the following messages: ' +
    messages.join(', ')
  );
}

send('Louis', 'meow', 'screech', 'purr');
          

...: give it a rest!

ellipsis is awesome

function send(recipient, ...messages) {
  console.log(recipient + ', you have the following messages: ' +
    messages.join(', ')
  );
}

send('Louis', 'meow', 'screech', 'purr');
          

`: template strings are beautiful

blah + blech + more + blah: quit typing ticks

function send(recipient, ...messages) {
  const joined = messages.join(', ');
  console.log(recipient + ', you have the following messages: ' + joined);
}

send('Louis', 'meow', 'screech', 'purr');
          

`: template strings are beautiful

I ${love} backticks

function send(recipient, ...messages) {
  const joined = messages.join(', ');
  console.log(`${recipient}, you have the following messages: ${joined}`);
}

send('Louis', 'meow', 'screech', 'purr');
          

... returns: the object spread operator

The old way: default properties are a pain

function api(data) {
  // Make a new object, don't modify the input!
  const _data = {
    cat: data.cat || 'Louis',
    // Watch out for 0!
    priority: (data.priority !== undefined) ? data.priority : 1
  };
  console.log('Our API would receive:', _data);
}

api({});
api({ cat: 'SpySpy' });
api({ cat: 'Felix', priority: 0 });
api({ cat: 'Sammy', priority: 100000 });
          

... returns: the object spread operator

... spreads like butter

function api(data) {
  const _data = {
    cat: 'Louis',
    priority: 1,
    // Pull in all the properties of data
    ...data
  };
  console.log('Our API would receive:', _data);
}

api({});
api({ cat: 'SpySpy' });
api({ cat: 'Felix', priority: 0 });
api({ cat: 'Sammy', priority: 100000 });
          

"Unspreading:" object literal assignment

The old way: geez I only wanted one thing

function getCatInfo() {
  return {
    name: 'Louis',
    age: 3,
    pets: 5000000000
  };
}

const info = getCatInfo();
console.log(info.name);
          

"Unspreading:" object literal assignment

Control yourself, take only what you need from it

function getCatInfo() {
  return {
    name: 'Louis',
    age: 3,
    pets: 5000000000
  };
}

const { name } = getCatInfo();
console.log(name);
          

"Unspreading" arguments, aka passing options by name (sort of)

The old way: our beloved options object

// Long comment documenting the options usually goes here

function send(recipient, options, ...messages) {
  if (options.style === 'terse') {
    console.log(`${recipient}: ${messages.join(', ')}`);
  } else {
    console.log(`${recipient}: ${messages.join('\r\n')}`);
  }
}

send('Louis', { style: 'terse' },
  'Felix hissed at you', 'SpySpy hissed at you'
);
          

"Unspreading" arguments, aka passing options by name (sort of)

The new, self-documenting way

function send(recipient, { style }, ...messages) {
  if (style === 'terse') {
    console.log(`${recipient}: ${messages.join(', ')}`);
  } else {
    console.log(`${recipient}: ${messages.join('\r\n')}`);
  }
}

send('Louis', { style: 'terse' }, 'Felix hissed at you', 'SpySpy hissed at you');
          

Default values for arguments

The old way: watch out for zero!

function speak(message) {
  if (message === undefined) {
    message = 'miaow';
  }
  console.log(message);
}

speak();
speak('hisssss');
          

Default values for arguments

The new way: ... just look at it

function speak(message = 'miaow') {
  console.log(message);
}

speak();
speak('hisssss');
          

Accessing stuff that might not exist

The old way: walking on eggshells

const messages = [
  {
    speaker: {
      name: 'Joe Smith',
      job: {
        title: 'Dentist'
      }
    }
  },
  {
    speaker: {
      name: 'Jane Doe'
    }
  }
];

messages.forEach(function(message) {
  console.log(`Job Title: ${message.speaker && message.speaker.job && message.speaker.job.title}`);
});
          

Accessing stuff that might not exist

The new way: optional chaining

const messages = [
  {
    speaker: {
      name: 'Joe Smith',
      job: {
        title: 'Dentist'
      }
    }
  },
  {
    speaker: {
      name: 'Jane Doe'
    }
  }
];

for (const message of messages) {
  console.log(`Job Title: ${message?.speaker?.job?.title}`);
}
          

This one is more recent

Iterating over object properties and values

The old-ish (IE11) way

var data = {
  name: 'Louis',
  handsomeness: '10',
  orange: true
};

Object.keys(data).forEach(function(key) {
  console.log(key + ': ' + data[key]);
});
          

Iterating over object properties and values

The new way: deconstructed galaxy brain

const data = {
  name: 'Louis',
  handsomeness: '10',
  orange: true
};

for (const [ key, value ] of Object.entries(data)) {
  console.log(`${key}: ${value}`);
}
          

Keeping only unique values in an array

The old, ugly way

var data = [ 'Louis', 'SpySpy', 'Pyro', 'Louis' ];
console.log('Before: ' + data.join(', '));
var newData = [];
// OK in IE11
data.forEach(function(datum) {
  if (newData.indexOf(datum) === -1) {
    newData.push(datum);
  }
});
console.log('After: ' + newData.join(', '));
// Who am I kidding, you'd probably use lodash
          

Keeping only unique values in an array

The new way: join the jet Set

const data = [ 'Louis', 'SpySpy', 'Pyro', 'Louis' ];
console.log('Before: ' + data.join(', '));
let newData = [ ... new Set(data) ];
console.log('After: ' + newData.join(', '));
// Bye-bye lodash (for most use cases)
          

async/await: write normal-looking code with APIs!

The old way: thinking about promises all day

function api(action, data) {
  // Asynchronous, like a real API call
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let reversed = '';
      for (let i = (data.cat.length - 1); (i >= 0); i--) {
        reversed += data.cat.charAt(i);
      }
      resolve(reversed);
    }, 500);
  });
}
          
// Reversing cats is very hard work,
// must call special remote API
api('reverse', { cat: 'Louis' }).then(function(reversed) {
  console.log(reversed);
  return api('reverse', { cat: 'SpySpy' });
}).then(function(reversed) {
  console.log(reversed);
  return api('reverse', { cat: 'Sammy' });
}).then(function(reversed) {
  console.log(reversed);
}).catch(function(err) {
  // Call the vet
});
          

async/await: write normal-looking code with APIs!

The readable way

function api(action, data) {
  // Asynchronous, like a real API call
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let reversed = '';
      for (let i = (data.cat.length - 1); (i >= 0); i--) {
        reversed += data.cat.charAt(i);
      }
      resolve(reversed);
    }, 500);
  });
}
          
async function go() {
  console.log(await api('reverse', { cat: 'Louis' }));
  console.log(await api('reverse', { cat: 'SpySpy' }));
  console.log(await api('reverse', { cat: 'Sammy' }));
}

go();
          

async/await: meet for...of, your new BFF

The old way: smash into that API rate limit!

const rateLimit = 2;
let pending = 0;
function api(action, data) {
  // Asynchronous, like a real API call
  return new Promise((resolve, reject) => {
    if (pending >= rateLimit) {
      return reject('RATE LIMITED');
    }
    pending++;
    setTimeout(() => {
      let reversed = '';
      for (let i = (data.cat.length - 1); (i >= 0); i--) {
        reversed += data.cat.charAt(i);
      }
      pending--;
      resolve(reversed);
    }, 500);
  });
}
          
// works
const twoCats = [ 'Louis', 'SpySpy' ];
Promise.all(
  twoCats.map(cat => api('reverse', { cat: cat }))
).then(reversed => {
  console.log(reversed);
  // hits the rate limit
  const threeCats = [ 'Louis', 'SpySpy', 'Sammy' ];
  return Promise.all(
    threeCats.map(cat => api('reverse', { cat: cat }))
  );
}).then(reversed => {
  console.log(reversed);
}).catch(e => {
  console.log(`Call the vet! ${e}`);
});
          

async/await: meet for...of, your new BFF

One at a time with for...of

const rateLimit = 2;
let pending = 0;
function api(action, data) {
  // Asynchronous, like a real API call
  return new Promise((resolve, reject) => {
    if (pending >= rateLimit) {
      return reject('RATE LIMITED');
    }
    pending++;
    setTimeout(() => {
      let reversed = '';
      for (let i = (data.cat.length - 1); (i >= 0); i--) {
        reversed += data.cat.charAt(i);
      }
      pending--;
      resolve(reversed);
    }, 500);
  });
}
          
async function go() {
  const threeCats = [ 'Louis', 'SpySpy', 'Sammy' ];
  // Note: this trick does not work with forEach
  for (const cat of threeCats) {
    // OMG SO... NORMAL
    console.log(await api('reverse', { cat: cat }));
  }
}

go();
          

Thank You