JS Summit: ES7 and Beyond!

JS Summit: ES7 and Beyond!

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

February 24, 2016
Tweet

Transcript

  1. 5.
  2. 7.

    TC39 Process • Five stage process for converting an idea

    to a standard feature. • https://tc39.github.io/process-document
  3. 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 }
  4. 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; } }
  5. 10.

    Stage 2 - Draft • Precise syntax and semantics; initial

    spec. • Object rest and spread properties let obj = { ...otherObj, x: 'foo' };
  6. 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); }
  7. 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
  8. 13.
  9. 14.

    Class Decorators • Annotations. • @expression that evaluates to a

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

  10. 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
  11. 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.
  12. 17.
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 27.

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

    } Object being modified (e.g. prototype or a class based on usage)
  20. 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
  21. 31.

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

    Object being modified (e.g. prototype or a class based on usage)
  22. 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 }
  23. 33.

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

    Optional if you mutate descriptor. Useful if you want to create new descriptor.
  24. 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}`; } ≈
  25. 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}`; } ≈
  26. 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.
  27. 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 ); }; }
  28. 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 ); }; }
  29. 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 ); }; }
  30. 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); } }
  31. 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); } }
  32. 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); } }
  33. 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
  34. 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
  35. 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
  36. 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

  37. 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' }));
  38. 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' }));
  39. 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 }));
  40. 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
  41. 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
  42. 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
  43. 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 } );
  44. 56.

    Usage: Default Options function createDog(options) { const defaults = {

    breed: 'Sheltie', name: 'Tucker' }; return { ...defaults, ...options }; }
  45. 57.

    Usage: Default Options createDog(); // { breed: 'Sheltie', name: 'Tucker'

    } createDog({ breed: 'Golden Retriever', name: 'Shadow' }); createDog({ name: 'Chance' }); // { breed: 'Sheltie', name: 'Chance' }
  46. 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; } }
  47. 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.
  48. 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
  49. 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); } } ) } }) }
  50. 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; } }
  51. 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.
  52. 72.

    Invoke function. async function printOrder(orderId) { const order = await

    fetchOrder(orderId); console.log(order); } printOrder(1);
  53. 75.

    Wrap awaited expression in Promise. async function printOrder(orderId) { const

    promise = Promise.resolve( fetchOrder(orderId) ); console.log(order); } printOrder(1);
  54. 76.

    Wrap remaining code in a then callback. async function printOrder(orderId)

    { const promise = Promise.resolve( fetchOrder(orderId) ); console.log(order); } printOrder(1);
  55. 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.
  56. 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);
  57. 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); } }
  58. 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); } }
  59. 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); } }
  60. 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); } }
  61. 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]);
  62. 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.
  63. 87.

    Parallel async function printOrders(orderIds) { const orders = await Promise.all(

    orderIds.map(id => fetchOrder(id)) ); console.log(orders); } printOrders([1, 2, 3]);
  64. 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.”
  65. 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.
  66. 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!
  67. 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