JS Summit: ES7 and Beyond!

JS Summit: ES7 and Beyond!

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

February 24, 2016
Tweet

Transcript

  1. ES7 and Beyond! Jeremy Fairbank blog.jeremyfairbank.com @elpapapollo github.com/jfairbank

  2. We help brands excel. simplybuilt.com Your website, SimplyBuilt. pushagency.io

  3. ES2016 ES.next ES.later ES.whatisgoingon? ES7 ¯\_(ϑ)_/¯

  4. Many proposed features coming later.

  5. None
  6. ECMA-262 TC39

  7. TC39 Process • Five stage process for converting an idea

    to a standard feature. • https://tc39.github.io/process-document
  8. Stage 0 - Strawman • Informal idea or proposal for

    change or addition. • Function bind operator 
 • Do expressions foo::bar(); let y = do { let x = 40; x + 2 }
  9. Stage 1 - Proposal • Make a case for addition

    by describing use cases, challenges, high-level API. • Observable • Class property declarations • Class and property decorators
 
 class Foo { @memoize bar() { return 42; } }
  10. Stage 2 - Draft • Precise syntax and semantics; initial

    spec. • Object rest and spread properties let obj = { ...otherObj, x: 'foo' };
  11. Stage 3 - Candidate • Completed spec; needs reviewing for

    refinements. • SIMD - Single instruction, multiple data • Async Functions
 
 async function loadOrders() { const orders = await fetchJson('/orders'); console.log(orders); }
  12. Stage 4 - Finished • Ready to be included in

    the next ES standard. • Exponentiation operator
 • Array.prototype.includes 3 ** 2; // 9 [1, 2, 3].includes(2); // true
  13. None
  14. Class Decorators • Annotations. • @expression that evaluates to a

    function. • Precedes a class or property. • github.com/wycats/javascript-decorators
 

  15. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } get fullName() { return `${this.firstName} ${this.lastName}`; } } const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank
  16. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } get fullName() { return `${this.firstName} ${this.lastName}`; } } const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank Getters are nonenumerable by default.
  17. Solution?

  18. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } } Object.defineProperty(Person.prototype, 'fullName', { enumerable: true, get() { return `${this.firstName} ${this.lastName}`; } }); const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank // fullName: Jeremy Fairbank
  19. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } } Object.defineProperty(Person.prototype, 'fullName', { enumerable: true, get() { return `${this.firstName} ${this.lastName}`; } }); const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank // fullName: Jeremy Fairbank
  20. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } } Object.defineProperty(Person.prototype, 'fullName', { enumerable: true, get() { return `${this.firstName} ${this.lastName}`; } }); const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank // fullName: Jeremy Fairbank
  21. More complex and less declarative.

  22. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } @enumerable get fullName() { return `${this.firstName} ${this.lastName}`; } } const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank // fullName: Jeremy Fairbank
  23. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } @enumerable get fullName() { return `${this.firstName} ${this.lastName}`; } } const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank // fullName: Jeremy Fairbank
  24. class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName

    = lastName; } @enumerable get fullName() { return `${this.firstName} ${this.lastName}`; } } const p = new Person('Jeremy', 'Fairbank'); for (const key in p) { console.log(`${key}: ${p[key]}`); } // firstName: Jeremy // lastName: Fairbank // fullName: Jeremy Fairbank
  25. How do decorators work?

  26. function enumerable(target, name, descriptor) { descriptor.enumerable = true; return descriptor;

    } Legacy Proposal (Babel 5 Support)
  27. function enumerable(target, name, descriptor) { descriptor.enumerable = true; return descriptor;

    } Object being modified (e.g. prototype or a class based on usage)
  28. function enumerable(target, name, descriptor) { descriptor.enumerable = true; return descriptor;

    } Name of property that will be added to target
  29. { get() { return `${this.firstName} ${this.lastName}`; }, set: undefined, enumerable:

    false, configurable: true } function enumerable(target, name, descriptor) { descriptor.enumerable = true; return descriptor; } Object literal defining the property that will be added to target
  30. Current Proposal (No Babel Support) function enumerable(target, descriptor) { descriptor.enumerable

    = true; return descriptor; }
  31. function enumerable(target, descriptor) { descriptor.enumerable = true; return descriptor; }

    Object being modified (e.g. prototype or a class based on usage)
  32. function enumerable(target, descriptor) { descriptor.enumerable = true; return descriptor; }

    Object literal defining the property that will be added to target { type: 'accessor', hint: 'getter', property: { name: () => 'fullName', initializer: () => ({ get() { return `${this.firstName} ${this.lastName}`; } }), get(obj) { return obj.fullName; }, set(obj, value) { obj.fullName = value; } }, enumerable: false, configurable: true }
  33. function enumerable(target, descriptor) { descriptor.enumerable = true; return descriptor; }

    Optional if you mutate descriptor. Useful if you want to create new descriptor.
  34. let descriptor = { property: { name: () => 'fullName',

    initializer: () => ({ get() { return `${this.firstName} ${this.lastName}`; } }), get(...) { ... } set(...) { ... } }, enumerable: false, configurable: true }; descriptor = enumerable(Person.prototype, descriptor) || descriptor; Object.defineProperty(Person.prototype, descriptor); @enumerable get fullName() { return `${this.firstName} ${this.lastName}`; } ≈
  35. let descriptor = { property: { name: () => 'fullName',

    initializer: () => ({ get() { return `${this.firstName} ${this.lastName}`; } }), get(...) { ... } set(...) { ... } }, enumerable: false, configurable: true }; descriptor = enumerable(Person.prototype, descriptor) || descriptor; Object.defineProperty(Person.prototype, descriptor); @enumerable get fullName() { return `${this.firstName} ${this.lastName}`; } ≈
  36. Parameterized Decorators

  37. class Person { constructor(name) { this.name = name; } @alias('sayHello')

    greet() { console.log(`Hello. My name is ${this.name}.`); } } const person = new Person('Jeremy'); person.greet(); // Hello. My name is Jeremy. person.sayHello(); // Hello. My name is Jeremy.
  38. function alias(aliasName) { return (target, descriptor) => { const name

    = descriptor.property.name(); const newDescriptor = Object.assign({}, descriptor, { value(...args) { return this[name](...args); } }); Object.defineProperty( target, aliasName, newDescriptor ); }; }
  39. function alias(aliasName) { return (target, descriptor) => { const name

    = descriptor.property.name(); const newDescriptor = Object.assign({}, descriptor, { value(...args) { return this[name](...args); } }); Object.defineProperty( target, aliasName, newDescriptor ); }; }
  40. function alias(aliasName) { return (target, descriptor) => { const name

    = descriptor.property.name(); const newDescriptor = Object.assign({}, descriptor, { value(...args) { return this[name](...args); } }); Object.defineProperty( target, aliasName, newDescriptor ); }; }
  41. Decorated Classes

  42. const Quadrupedal = { walk() { console.log(`${this.name} walks with four

    legs`); } }; @mixin(Quadrupedal, EventEmitter.prototype) class Dog { constructor(name, age) { this.name = name; this.age = age; } haveBirthday() { this.age++; this.emit('birthday', this); } }
  43. const Quadrupedal = { walk() { console.log(`${this.name} walks with four

    legs`); } }; @mixin(Quadrupedal, EventEmitter.prototype) class Dog { constructor(name, age) { this.name = name; this.age = age; } haveBirthday() { this.age++; this.emit('birthday', this); } }
  44. const Quadrupedal = { walk() { console.log(`${this.name} walks with four

    legs`); } }; @mixin(Quadrupedal, EventEmitter.prototype) class Dog { constructor(name, age) { this.name = name; this.age = age; } haveBirthday() { this.age++; this.emit('birthday', this); } }
  45. const dog = new Dog('Tucker', 8); dog.on('birthday', d => {

    console.log(`${d.name} just turned ${d.age}`); }); dog.haveBirthday(); // Tucker just turned 9 dog.walk(); // Tucker walks with four legs
  46. const dog = new Dog('Tucker', 8); dog.on('birthday', d => {

    console.log(`${d.name} just turned ${d.age}`); }); dog.haveBirthday(); // Tucker just turned 9 dog.walk(); // Tucker walks with four legs From EventEmitter From Quadrupedal
  47. Why Class Decorators? • Extremely powerful and expressive syntax for

    class/property modification. • Mixins — avoid fragile base class or multiple inheritance issues. • @autobind, @deprecate, @memoize, @readonly, and more! • github.com/jayphelps/core-decorators.js
  48. Object Rest and Spread • Rest and spread capabilities like

    arrays in ES6. • Use ... operator. • Rest operation is like pick function in lodash. • Spread is like a clone function. • Syntactical sugar for Object.assign. • github.com/sebmarkbage/ecmascript-rest-spread

  49. Rest - Destructuring import isEqual from 'lodash/lang/isEqual'; const assert =

    ::console.assert; const obj = { a: 42, b: 'foo', c: 5, d: 'bar' }; const { a, b, ...rest } = obj; assert(a === 42); assert(b === 'foo'); assert(isEqual(rest, { c: 5, d: 'bar' }));
  50. Rest - Destructuring import isEqual from 'lodash/lang/isEqual'; const assert =

    ::console.assert; const obj = { a: 42, b: 'foo', c: 5, d: 'bar' }; const { a, b, ...rest } = obj; assert(a === 42); assert(b === 'foo'); assert(isEqual(rest, { c: 5, d: 'bar' }));
  51. Spread const obj = { x: 42, y: 100 };

    const newObj = { z: 5, ...obj }; const otherObj = { ...obj, x: 13 }; assert(isEqual(newObj, { x: 42, y: 100, z: 5 })); assert(isEqual(otherObj, { x: 13, y: 100 }));
  52. const obj = { x: 42, y: 100 }; const

    newObj = { z: 5, ...obj }; const otherObj = { ...obj, x: 13 }; assert(isEqual(newObj, { x: 42, y: 100, z: 5 })); assert(isEqual(otherObj, { x: 13, y: 100 })); Spread
  53. const obj = { x: 42, y: 100 }; const

    newObj = { z: 5, ...obj }; const otherObj = { ...obj, x: 13 }; assert(isEqual(newObj, { x: 42, y: 100, z: 5 })); assert(isEqual(otherObj, { x: 13, y: 100 })); Spread
  54. const obj = { x: 42, y: 100 }; const

    newObj = { z: 5, ...obj }; const otherObj = { ...obj, x: 13 }; assert(isEqual(newObj, { x: 42, y: 100, z: 5 })); assert(isEqual(otherObj, { x: 13, y: 100 })); Spread
  55. Transpiling Spread const newObj = { z: 5, ...obj };

    const newObj = Object.assign({ z: 5 }, obj); const otherObj = { ...obj, x: 13 }; const otherObj = Object.assign({}, obj, { x: 13 } );
  56. Usage: Default Options function createDog(options) { const defaults = {

    breed: 'Sheltie', name: 'Tucker' }; return { ...defaults, ...options }; }
  57. Usage: Default Options createDog(); // { breed: 'Sheltie', name: 'Tucker'

    } createDog({ breed: 'Golden Retriever', name: 'Shadow' }); createDog({ name: 'Chance' }); // { breed: 'Sheltie', name: 'Chance' }
  58. Usage: Redux const INITIAL_CONTACT = { name: '', email: ''

    }; function reducer(state = INITIAL_CONTACT, action) { switch(action.type) { case 'UPDATE_NAME': return { ...state, name: action.payload }; case 'UPDATE_EMAIL': return { ...state, email: action.payload }; default: return state; } }
  59. Why Object Rest/Spread? • Succinct syntax instead of Object.assign calls.

    • Pick/omit object properties. • Clone objects. (WARNING: Not a deep clone.) • Provide overridable default options in a function.
  60. Async Functions • Provide synchronous-like syntax for asynchronous code. •

    Prepend function with async. • Use await operator on a promise to obtain a fulfilled promise value. • github.com/tc39/ecmascript-asyncawait
  61. function fetchCustomerNameForOrder(orderId, done, fail) { fetchOrder(orderId, function(err, order) { if

    (err) { logError(err); fail(err); } else { fetchCustomer( order.customerId, function(err, customer) { if (err) { logError(err); fail(err); } else { done(customer.name); } } ) } }) }
  62. function fetchCustomerNameForOrder(orderId) { return fetchOrder(orderId) .then(order => fetchCustomer(order.customerId)) .then(customer =>

    customer.name) .catch(err => { logError(err); throw err; }); }
  63. Workarounds

  64. async function fetchCustomerNameForOrder(orderId) { let order, customer; try { order

    = await fetchOrder(orderId); } catch(err) { logError(err); throw err; } try { customer = await fetchCustomer(order.customerId); return customer.name; } catch(err) { logError(err); throw err; } }
  65. Synchronous-looking

  66. async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);

    } printOrder(1);
  67. async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);

    } printOrder(1); Declare a function as async
  68. async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);

    } printOrder(1); Await a promise and obtain the fulfilled/ resolved value. Nonblocking.
  69. async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);

    } printOrder(1); x x
  70. How do async functions work at a high level?

  71. async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);

    } printOrder(1);
  72. Invoke function. async function printOrder(orderId) { const order = await

    fetchOrder(orderId); console.log(order); } printOrder(1);
  73. async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);

    } printOrder(1); Encounter await.
  74. async function printOrder(orderId) { const order = await fetchOrder(orderId); console.log(order);

    } printOrder(1); Wrap awaited expression in Promise.
  75. Wrap awaited expression in Promise. async function printOrder(orderId) { const

    promise = Promise.resolve( fetchOrder(orderId) ); console.log(order); } printOrder(1);
  76. Wrap remaining code in a then callback. async function printOrder(orderId)

    { const promise = Promise.resolve( fetchOrder(orderId) ); console.log(order); } printOrder(1);
  77. function printOrder(orderId) { const promise = Promise.resolve( fetchOrder(orderId) ); return

    promise.then(order => { console.log(order); return Promise.resolve(undefined); }); } printOrder(1); Wrap remaining code in a then callback.
  78. No explicit return, so return a resolved undefined value. function

    printOrder(orderId) { const promise = Promise.resolve( fetchOrder(orderId) ); return promise.then(order => { console.log(order); return Promise.resolve(undefined); }); } printOrder(1);
  79. Error Handling

  80. function printOrder(orderId) { fetchOrder(orderId) .then(order => console.log(order)) .catch(e => console.log('error

    retrieving order', e)); } async function printOrder(orderId) { try { const order = await fetchOrder(orderId); console.log(order); } catch(e) { console.log('error retrieving order', e); } }
  81. function printOrder(orderId) { fetchOrder(orderId) .then(order => console.log(order)) .catch(e => console.log('error

    retrieving order', e)); } async function printOrder(orderId) { try { const order = await fetchOrder(orderId); console.log(order); } catch(e) { console.log('error retrieving order', e); } }
  82. function printOrder(orderId) { fetchOrder(orderId) .then(order => console.log(order)) .catch(e => console.log('error

    retrieving order', e)); } async function printOrder(orderId) { try { const order = await fetchOrder(orderId); console.log(order); } catch(e) { console.log('error retrieving order', e); } }
  83. function printOrder(orderId) { fetchOrder(orderId) .then(order => console.log(order)) .catch(e => console.log('error

    retrieving order', e)); } async function printOrder(orderId) { try { const order = await fetchOrder(orderId); console.log(order); } catch(e) { console.log('error retrieving order', e); } }
  84. Sequential vs. Parallel

  85. Sequential async function printOrders(orderIds) { const orders = []; for

    (const id of orderIds) { const order = await fetchOrder(id); orders.push(order); } console.log(orders); } printOrders([1, 2, 3]);
  86. Sequential async function printOrders(orderIds) { const orders = []; for

    (const id of orderIds) { const order = await fetchOrder(id); orders.push(order); } console.log(orders); } printOrders([1, 2, 3]); Each step of loop has to wait. Issue if requests aren’t dependent on each other.
  87. Parallel async function printOrders(orderIds) { const orders = await Promise.all(

    orderIds.map(id => fetchOrder(id)) ); console.log(orders); } printOrders([1, 2, 3]);
  88. Parallel async function printOrders(orderIds) { const orders = await Promise.all(

    orderIds.map(id => fetchOrder(id)) ); console.log(orders); } printOrders([1, 2, 3]); Make all requests at once and resolve with Promise.all. Allows for “parallelism.”
  89. Sequential vs. Parallel Demo

  90. Moral: don’t use sequential awaits unless you need serialization!

  91. Why Async Functions? • Synchronous-looking asynchronous code. • Gain back

    use of native language flow control constructs with asynchronous code (e.g. for..of, try/catch). • Asynchronous coroutines.
  92. Other notable features • Stage 4: Exponentiation operator, Array.prototype.includes •

    Stage 3: SIMD • Stage 2: Shared memory and atomics • Stage 1: Class property initializers, callable class constructors, Observable • Stage 0: function bind operator, do expressions, function decorators, method parameter decorators • Many more!
  93. Resources • https://github.com/tc39/ecma262 • https://tc39.github.io/ecma262/ • http://babeljs.io/ • https://tc39.github.io/process-document/ •

    https://esdiscuss.org/ • http://www.ecma-international.org/memento/ contribute_TC39_Royalty_Free_Task_Group.php
 

  94. Thanks! Code: github.com/jfairbank/es7-and-beyond-talk Jeremy Fairbank blog.jeremyfairbank.com @elpapapollo github.com/jfairbank