Lets Talk about ...

Whats new (and upcoming) in JS

2019 Edition

Stages

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

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

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

current

promise = new Promise((resolve, reject) => {
  throw new Error('i failed');
});
promise.catch(console.log); // → i failed

Promise.try

promise = Promise.try(() => {
  throw new Error('i failed');
});
promise.catch(console.log); // → i failed
promise = Promise.try(() => {
  return 'result';
});
promise.then(console.log);  // → result

Promise.all

(ES 2015)

Promise.race

(ES 2015)

Promise.any

Promise.allSettled

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

Manipulating

Comparing

(Weak)Map

Extensions

Array-like

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

Use cases

Example

let g = window; // outer global
let r = new Realm(); // root realm

let f = r.evaluate("(function() { return 17 })");

f() === 17 // true
Error.prototype.stack

String

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

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?