Lets Talk about ...
Whats new (and upcoming) in JS
2019 Edition
Stages
- Stage 0: Strawperson (Nothing/PoC)
- Stage 1: Proposal (Polyfill)
- Stage 2: Draft (Experimental)
- Stage 3: Candidate (Spec compliant)
- Stage 4: Finished (Shipping)
Stage 0 (Strawperson)
Nested Import
Currently
import { strictEqual } from 'assert';
import { check as checkC } from './client.js';
import { check as checkS } from './server.js';describe('fancy feature #5', () => {
  it('should work on the client', () => {
    strictEqual(checkC(), 'client ok');
  });
  it('should work on the server', () => {
    strictEqual(checkS(), 'server ok');
  });
});With nested imports
describe('fancy feature #5', () => {
  import { strictEqual } from 'assert';
  it('should work on the client', () => {
    import { check } from './client.js';
    strictEqual(check(), 'client ok');
  });
  it('should work on the server', () => {
    import { check } from './server.js';
    strictEqual(check(), 'server ok');
  });
});Advantages
- Lazy evaluation
- Scoped to enclosing block
- Backwards compatible
Stage 1 (Proposal)
Math
Extensions
Seeded (Pseudo) Random Numbers
Generates an enumerable based on the passed seed
for (const [i,r] of enumerate(
  Math.seededPRNG({seed:0})
)) {
  // do something with each value
  if(i >= limit) break;
}Clamp
Makes sure value stays in given range
Math.clamp(x, lower, upper);
Math.clamp(-10, 0, 100);  // → 0
Math.clamp(1000, 0, 100); // → 100
Math.clamp(0, 10, 100);   // → 10
Scale
Maps value from input range to output range
Math.scale(x, inLow, inHigh, outLow, outHigh);
Math.scale(0.25, 0, 1, 0, 100);    // → 25
Math.scale(0.25, 0, 1, -180, 180); // → -90
Iterator
Extensions
Sample Iterator
function* naturals() {
  let i = 0;
  while (true) {
    yield i;
    i += 1;
  }
}for (const i of naturals) {
  // do something with each value
  if(i >= limit) break;
}Filter
Returns iterator which skips some values of source iterator
const evens = naturals()
  .filter((n) => n % 2 === 0);
for (const i of evens) {
  // do something with each value
  if(i >= limit) break;
}Other extensions
- map
- count
- min / max
- find
- skip / take
Observable
Mix of Promise and Iterator
Create Observable
let observable = new Observable(observer => {
  const {next, error, complete } = observer;
  // Emit a single value after 1 second
  let timer = setTimeout(() => {
    next('hello');
    complete();
  }, 1000);
  // Return cleanup function
  return () => clearTimeout(timer);
});Observable.prototype.subscribe
let subscription = observable.subscribe(
  x => console.log(x),
  err => console.log(`Failed: ${err}`),
  () => console.log('Finished')
);Observable.prototype.forEach
observable.forEach(x => {
  console.log(`Received value: ${ x }`);
}).then(() => {
  console.log('Finished successfully')
}).catch(err => {
  console.log(`Finished with error: ${ err }`);
})Observable.prototype.filter
observable.filter(callback)
Observable.of(1, 2, 3).filter(value => {
  return value > 2;
}).subscribe(value => {
  console.log(value);
});// → 3
Observable.prototype.map
Observable.of(1, 2, 3).map(value => {
  return value * 2;
}).subscribe(value => {
  console.log(value);
});// → 2 → 4 → 6
Observable.prototype.reduce
Observable.of(1, 2, 3).reduce((prev, curr) => {
  return prev + curr;
}).subscribe(result => {
  console.log(result);
});// → 6
Observable.prototype.concat
Observable.of(1, 2, 3).concat(
  Observable.of(4, 5, 6),
  Observable.of(7, 8, 9)
).subscribe(result => {
  console.log(result);
});// → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9
Promise
Extensions
try / attempt
- Converts function to promise
- and sync errors to async
current
promise = new Promise((resolve, reject) => {
  throw new Error('i failed');
});
promise.catch(console.log); // → i failedPromise.try
promise = Promise.try(() => {
  throw new Error('i failed');
});
promise.catch(console.log); // → i failedpromise = Promise.try(() => {
  return 'result';
});
promise.then(console.log);  // → resultPromise.all
(ES 2015)
- returns all results as array
- short-circuits on reject
Promise.race
(ES 2015)
- resolves first settled promise
- rejects if first settled promise rejects
Promise.any
- rejects when no promise resolves
- short-circuits on resolve
Promise.allSettled
- returns all results as array
- does not short circuit
Stage 2 (Draft)
Decorators
Temporal
(like native MomentJS)
Classes
Instant        // Point in time
OffsetDateTime // Offset from UTC (+01:00)
CivilDateTime  // Offset from Calendar
ZonedDateTime  // Offset + Timezone (Europe/Rome)
(Weak)Set
Extensions
Array-like
- filter
- map
- find
- reduce
- join
- ...
Manipulating
- addAll(... elements)
- deleteAll(... elements)
- intersection
- union
- difference
- ...
Comparing
- isSubsetOf
- isDisjointFrom
- isSupersetOf
(Weak)Map
Extensions
Array-like
- of / from
- filter
- mapKeys / mapValues
- reduce
- join
- ...
Manipulating
Aggregation
throw
in expression contexts
Parameter initializers
function save(filename =
  throw new TypeError('Argument required')
) {}Arrow function bodies
lint(ast, {
  with: () => throw new Error(
    "avoid using 'with' statements.
  ")
});Conditional expressions
const obj = str
  ? JSON.parse(str)
  : throw new Error('String required')
;Logical operations
class Product {
  get id() { return this._id; }
  set id(value) {
    this._id = value ||
      throw new Error("Invalid value")
    ;
  }
}Function implementation hiding
Function.prototype.toString
Error.prototype.stack
function foo() {
  "hide implementation";
  // ...
}Realms
Intuitions
- sandbox
- iframe without DOM
- principled version of Node's 'vm' module
- sync worker
Use cases
- security isolation
- plugins
- in browser code editors
- server-side rendering
- testing
Example
let g = window; // outer global
let r = new Realm(); // root realm
let f = r.evaluate("(function() { return 17 })");
f() === 17 // true
Error.prototype.stackString
Extensions
String.replaceAll
const query = 'q=query+string+parameters';
const withSpaces = query.replace(/\+/g, ' ');
const withSpaces = query.replaceAll('+', ' ');Optional
Chaining
Current
var query = 'input[name=foo]';
var $input = $form.querySelector(query)
var value = $input ? $input.value : undefined
With optional chaining
var fooValue = $form.querySelector(query)?.value
var street = user?.address?.street
iterator.return?.() // manually close an iterator
Nullish
Coalescing
current
const undefinedValue =
  response.settings?.undefinedValue
  || 'some other default'
; // result: 'some other default'
const nullValue =
  response.settings?.nullValue
  || 'some other default'
; // result: 'some other default'
current problems
response.settings?.showSplashScreen || true
// False evaluates to false, result: true
response.settings?.animationDuration || 300;
// 0 evaluates to false, result: 300
response.settings?.headerText || 'Hello, world!'
// '' evaluates to false, result: 'Hello, world!'
Nullish Coalescing
response.settings?.showSplashScreen ?? true
response.settings?.animationDuration ?? 300;
response.settings?.headerText ?? 'Hello, world!'
Stage 3 (Candidate)
Numeric
Separators
current
1000000000
101475938.38
// Is this a billion? a hundred millions? Ten millions?
let fee = 12300;
// is this 12,300? Or 123, because it's in cents?
let amount = 1234500;
// is this 1,234,500? Or cents, hence 12,345?
// Or financial, 4-fixed 123.45?
Numeric Separators
1_000_000_000           // Ah, so a billion
101_475_938.38          // hundreds of millions
let fee = 123_00;       // $123 (12300 cents)
let fee = 12_300;       // $12,300 (thats much)
let amount = 12345_00;  // 12,345 (1234500 cents)
let amount = 123_4500;  // 123.45 (4-fixed fin.)
let amount = 1_234_500; // 1,234,500
Numeric Separators
0.000_001 // fractions
1e10_000  // exponents
let nibbles = 0b1010_0001_1000_0101; // binary
let brandColor = 0x44_BB_44; // hex (colors)
Class
Extensions
private
in classes or objects
class ColorFinder {
  static #red = "#ff0000";
  static #blue = "#00ff00";
  static #green = "#0000ff";class ColorFinder {
  static #red = "#ff0000";
  static #blue = "#00ff00";
  static #green = "#0000ff";
  static colorName(name) {
    switch (name) {
      case "red": return ColorFinder.#red;
      case "blue": return ColorFinder.#blue;
      case "green": return ColorFinder.#green;
      default: throw new RangeError("unknown color");
    }
  }
}Stage 4 (Shipping)
Optional catch binding
try { ... } catch (unused) { ... }try { ... } catch { ... }Intl
Internationalization aka I18l
Langauge dependent formatting for
- Dates / Times
- Numbers
- Plural Rules
- Collator (comparing)
Intl.DateTimeFormat
const DateFormat = Intl.DateTimeFormat;
new DateFormat('en-US').format(date);
// → '12/20/2019'new DateFormat('en-GB').format(date);
// → '20/12/2019'// Include a fallback language
new DateFormat(['foo', 'de']).format(date)
// → '20.12.2019'
Intl.NumberFormat
new Intl.NumberFormat('de-DE', {
  style: 'currency', currency: 'EUR'
}).format(number); // → '123.456,79 €'// the Japanese yen doesn't use a minor unit
new Intl.NumberFormat('ja-JP', {
  style: 'currency', currency: 'JPY'
}).format(number); // → '¥123,457'new Intl.NumberFormat('en-IN', {
  maximumSignificantDigits: 3
}).format(number); // → '1,23,000'Intl.PluralRules
new Intl.PluralRules('en-US').select(0);
// → 'other'new Intl.PluralRules('en-US').select(1);
// → 'one'new Intl.PluralRules('en-US').select(2);
// → 'other'Intl.Collator
function letterSort(lang, letters) {
  letters.sort(new Intl.Collator(lang).compare);
  return letters;
}console.log(letterSort('de', ['a','z','ä']));
// expected output: Array ["a", "ä", "z"]console.log(letterSort('sv', ['a','z','ä']));
// expected output: Array ["a", "z", "ä"]Array
Extensions
Array.prototype.flat
const array = [1, [2, [3, [4]]]];
array.flat();         // → [1, 2, [3, [4]]];
array.flat(2);        // → [1, 2, 3, [4]];
array.flat(Infinity); // → [1, 2, 3, 4];
Array.prototype.flatMap
const dup = (x) => [x, x];
[3, 4].map(dup);        // → [[3, 3], [4, 4]]
[3, 4].map(dup).flat(); // 🐌 → [3, 3, 4, 4]
[3, 4].flatMap(dup);    // 🚀 → [3, 3, 4, 4]
Object
Extensions
Object.prototype.entities
const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]
Object.prototype.fromEntities
const result = Object.fromEntries(entries);
// → { x: 42, y: 50 }
 
Thats it
Whats next?
Questions?