Flow syntax {| <data> |}
could be used in EcmaScript too to mean Object.freeze({ <data> })
Or maybe called crossRealmnInstanceof
. The objective is to have a way to determine if an object is an instance of a built-in even cross-realmns.
If I do Reflect.isOfBuiltinType(iframe.contentDocument, Document)
it should return true
even though iframe.contentDocument instanceof Document
will return false
because they are from different realmns
Sometimes you need to propagate an exception with some added context specific to your logic.
Such an example would be
function parseUserInput(data) {
// <snipped />
parsed = JSON.parse(data);
// <snipped />
}
But if the data is invalid, the developer might receive an error "Failed to parse JSON" without much context.
A solution would be to catch the error and add some information relevant to the function.
function parseUserInput(data) {
// <snipped />
try {
parsed = JSON.parse(data);
} catch (e) {
throw new Error('Could not parse user data. Make sure it\'s JSON!');
}
// <snipped />
}
But this hides the underlying Error which can also contain valuable information. Logging the previous error works but is a subpart experience for the developper.
Allow specifying which error caused the new one to throw.
function parseUserInput(data) {
// <snipped />
try {
parsed = JSON.parse(data);
} catch (e) {
// possibly
throw (new Error('Could not parse user data. Make sure it\'s JSON!')).causedBy(e);
}
// <snipped />
}
/*
output:
Uncaught Error: Expression <[name='english-test-exempt']> == 'y': Could not parse expected value 'y'. It must be valid JSON.
at ...
at ...
...
Caused By SyntaxError: Unexpected token ' in JSON at position 0
at ...
at ...
...
*/
Edit 1: A strawman for this idea has actually been proposed 4 years ago. Unfortunately, the conversation seems the have died out
Edit 2: Proposed https://esdiscuss.org/topic/proposal-for-a-null-coalescing-operator
Edit 3: Actual active proposal https://github.com/tc39/proposal-nullish-coalescing (works on both null & undefined)
We already have a very simple way to do what this operator does,
// val will be equal to DEFAULT_VAL if val is undefined.
const val = options.val || DEFAULT_VAL;
The problem is that the ||
operator will return the value of the second operand if the first one is falsy. Which is far from ideal for default values. Forcing developers to fallback to the much more verbose
// val will be equal to DEFAULT_VAL if val is falsy.
const val = options.val === void 0 ? DEFAULT_VAL : options.val;
As the first syntax works for most cases, it tends to be overused and can be find in places where it will cause problems.
The undefined coalesce operator, being as easy to use as the boolean OR operator, would fix that problem.
// val will be equal to DEFAULT_VAL if val is undefined.
const val = options.val ?? DEFAULT_VAL;
Edit: See previous point.
options.val ??= DEFAULT_VAL;
// being equivalent to
options.val = options.val ?? DEFAULT_VAL;
// which itself is equivalent to
options.val = options.val === void 0 ? DEFAULT_VAL : options.val;
See this: https://gist.github.com/Ephys/4d9731d5c7a945dcc752d279eaeaa08f
I'v been beaten to it: https://github.com/mathiasbynens/proposal-number-fromstring
Just like null
is, to replace global variables global.undefined
and global.NaN
Documentation: https://tc39.github.io/ecma262/#sec-reserved-words
SomeClass.prototype.{ dependencies, enable } = mixin;
Desugars to
SomeClass.prototype.dependencies = mixin.dependencies;
SomeClass.prototype.enable = mixin.enable;
A complementary version is being discussed on the ES mailing list https://esdiscuss.org/topic/extended-dot-notation-pick-notation-proposal
Having a pick operator seems like an important feature to avoid cases like this one https://github.com/sebmarkbage/ecmascript-rest-spread/blob/master/Issues.md
const aVar = someObj.{ a, b };
Desugars to
const aVar = {
a: someObj.a,
b: someObj.b
};
And a similar syntax proposes the following:
obj.{
a: 1,
[Symbol.equal]() { return true; },
c() {}
};
which desugars to
obj.a = 1;
obj[Symbol.equal] = function() { return true; };
obj.c = function c() {};
That syntax might not be compatible with the previous one as the following would be ambiguous:
const a = 1;
const b = 2;
const result = someObj.{ a, b };
Is this assigning a
and b
to someObj
or creating an object result
equal to { a: someObj.a, b: someObj.b }
?
Possible way it could be retrocompatible:
Symbol.toJSON = Symbol('toJSON');
Object.prototype[Symbol.toJSON] = function toJSON() {
if (this.toJSON) {
return this.toJSON();
}
return this;
}
const a = {};
a[Symbol.toJSON](); // a
const b = {
toJSON() {
return 'toJSONed';
}
}
b[Symbol.toJSON](); // 'toJSONed'
Another way would be that assigning a toJSON
method to any object also magically assigns [Symbol.toJSON]
(unless it was already present on the object) which does nothing but call this.toJSON
.
It would allow classes to have a static property called name
.
Symbol.name = Symbol('name');
Object.defineProperty(Function.prototype, Symbol.name, {
get() {
return this.name;
},
set(name) {
this.name = name;
}
})
(function test() {})[Symbol.name]; // 'test'
Useful for Object comparison as they might require some custom logic (Date, Buffer, RegExp, etc).
a[Symbol.equals](b)
a[Symbol.compare](b)
new Date('05 june 2016') === new Date('05 june 2016'); // false
new Date('05 june 2016')[Symbol.equals](new Date('05 june 2016')); // true
/aRegexp/ === /aRegexp/; // false
/aRegexp/[Symbol.equals](/aRegexp/); // true
Edit: A proposal for Reflect.isCallable
and Reflect.isConstructable
(much better names!) was created a year ago, conversation seems to have died out again.
- https://github.com/caitp/TC39-Proposals
- https://esdiscuss.org/topic/determine-if-a-value-is-callable-constructible
Function.isCallable
- To have a clean way to check if an object has a [[call]]
internal method.
Function.isClass
- To have a clean way to check if an object has a [[Construct]]
internal method.
The idea is to harmonise the syntax of typeof
and instanceof
. This follows the idea that typeof
should be used for primitives and instanceof
for objects.
// It would work like this:
aVar typeof aType;
Symbol('aSymbol') typeof 'symbol' // evaluates to true
// this syntax would be equivalent to
typeof Symbol('aSymbol') === 'symbol';
// We could also perhaps support using the primitive wrappers as the second-hand operand. Probably a terrible idea though
// The following expressions would all evaluate to true:
'string' typeof String
Symbol() typeof Symbol
4 typeof Number
true typeof Boolean
{} typeof Object
Object.create(null) typeof Object
void 0 typeof undefined
null typeof null
if (x !instanceof Date);
if (x !in someObject);
if (x !typeof Number);
Desugars to
if (!(x instanceof Date));
if (!(x in someObject));
// if (typeof x !== 'number'); // Never said all of my ideas were brilliant.
if (!(x typeof Number));
Edit: This has been discussed and is on hold tc39/proposal-async-await#9
Just an under-developed idea.
// app.js
import Database from './database';
import Server from './server';
await Database.init(); // Pause module execution until promise has either resolved or rejected.
await Server.run();
export function stop() {
Server.stop();
}
// index.js - Actual entry point
import { stop } from './app'; // Import is resolved once module is done executing. No more async import hell.
stop();
function namedUnpackedParameters(parameters: { queryParameters = {}, pathParameters = {} } = {}) {
console.log('parameters = ', parameters);
console.log('queryParameters = ', queryParameters);
console.log('pathParameters =', pathParameters);
}
namedUnpackedParameters();
// OUTPUT:
// parameters = { queryParameters: {}, pathParameters: {} }
// queryParameters = {}
// pathParameters = {}
namedUnpackedParameters({ cors: false, pathParameters: { id: 4 } });
// OUTPUT:
// parameters = { cors: false, queryParameters: {}, pathParameters: { id: 4 } }
// queryParameters = {}
// pathParameters = { id: 4 }
Desugars to
function namedUnpackedParameters(parameters = {}) {
const { queryParameters = {}, pathParameters = {} } = parameters;
console.log('parameters = ', parameters);
console.log('queryParameters = ', queryParameters);
console.log('pathParameters =', pathParameters);
}
class B mixes Observable extends A {
}
Currently, arrow functions cannot have names. Which is slightly annoying in some cases.
const onError = e => {
img.removeEventListener('error', onError);
img.removeEventListener('load', onLoad);
// handle error
this.submitError(e);
};
const onLoad = () => {
img.removeEventListener('error', onError);
img.removeEventListener('load', onLoad);
// do stuff
this.addImage(img);
};
img.addEventListener('error', onError);
img.addEventListener('load', onLoad);
Naming them would enable us to do the following:
img.addEventListener('error', onError(e) => {
img.removeEventListener('error', onError);
img.removeEventListener('load', onLoad);
// handle error
this.submitError(e);
});
img.addEventListener('load', onLoad() => {
img.removeEventListener('error', onError);
img.removeEventListener('load', onLoad);
// do stuff
this.addImage(img);
});
These functions would be block-scoped and cannot be redefined in the same scope. Would they be hoisted though ?
This is probably not web-compatible, but I'm writing it down anyway.
Instead of writing
function doTheThing() { /* ... */ }
doTheThing();
We could write
doTheThing() { /* ... */ }
doTheThing();
Or maybe
const doTheThing() { /* ... */ }
doTheThing();
Or even
let doTheThing() { /* ... */ }
doTheThing();
These later options would be possible and could solve a few issues. More research on the matter is required.
Possible Use Cases:
- Having a counterpart to SQL Enums for Node.
The current way to implement enums in JavaScript is to just use object literals, as it looks and is as easy to use as enums.
const A = {
ITEM_ONE = 0,
ITEM_TWO = 1,
ITEM_THREE = 2
}
Object.freeze(A);
This approach isn't brilliant for type systems however, as instanceof
does not work on them, nor can you hint properly at what kind of argument is expected.
A proper approach would use classes. However, emulating the right enum behavior is so complicated and troublesome that nobody actualy does it.
The idea is to put the enum
keyword into use to ensure the right behavior is respected.
enum A {
ITEM_ONE('cat');
ITEM_TWO('dog');
ITEM_THREE('bird');
// enums should be allowed to have custom properties
constructor(type) {
this.type = type;
}
// enums should be allowed to have methods
bark() {
console.log('bark!');
}
}
A.ITEM_ONE.type; // 'cat'
A.ITEM_ONE.bark(); // prints 'bark!'
A.ITEM_ONE.num; // 0, id representing the item order in the enum list.
A.ITEM_ONE.name; // 'ITEM_ONE'
// enums should be immutable
A.ITEN_ONE.type = 'elephant'; // Throws
// non extensibles
class B extends A {} // does it throw here ?
// can't make new instances
new A() // throws!
Object.create(A.prototype); // should this throw or work ?
Object.setPrototypeOf({}, A.prototype); // should this throw or work ?
desugars to
// new global obj
class Enum {
constructor(num, name) {
if (new.target === Enum) {
throw new TypeError('Enum cannot be instanciated directly');
}
this.num = num;
this.name = name;
}
}
const A = (function() {
let __locked = false;
let num = 0;
class A extends global.Enum {
constructor(name, id) {
if (new.target !== A) {
throw new TypeError('Cannot extend enum');
}
if (__locked === true) {
throw new TypeError('Cannot instanciate enums');
}
super(num++, name);
this.id = id;
Object.freeze(this);
}
}
Object.defineProperties(A, {
ITEM_ONE: {
enumerable: true,
writable: false,
configurable: false,
value: new A('ITEM_ONE', 0)
},
ITEM_TWO: {
enumerable: true,
writable: false,
configurable: false,
value: new A('ITEM_TWO', 1)
},
ITEM_THREE: {
enumerable: true,
writable: false,
configurable: false,
value: new A('ITEM_THREE', 2)
}
});
__locked = true;
Object.freeze(A);
Object.freeze(A.prototype);
return A;
}());
This is IMO a bad idea and I don't really want it because you should not have holes in your arrays but I wrote it down anyway
const i = 3;
const anArray = [
2: 'apple',
[i]: 'another apple',
];
anArray[0]; // undefined
anArray[1]; // undefined
anArray[2]; // 'apple'
anArray[3]; // 'another apple'
const anArray = [
indexedItem: 'apple', // throws ?
];
- setTimeout,
- setInterval
- setImmediate,
- nextTick,
- atob (but renamed, because seriously)
- btoa (same)
- WhatWG fetch (deprecate
http.request
) - WhatWG Streams (deprecate node streams)
WhatWG URL(done)
aka. const parameters. Fairly straigtforward.
Since ES6, most variable bindings can be immutable using const
, except arguments (not the arguments
binding itself though, that one is immutable in strict mode).
function aFunc(const aParam) {
// Uncaught TypeError: Assignment to constant variable.
aParam = 'another value';
}
Allow an else
statement to follow a for
statement. The else block gets executed if the loop did not iterate.
for (const item of myList) {
} else {
// ELSE BLOCK
}
// desugars to
let iterated = false;
for (const item of myList) {
iterated = true;
}
if (!iterated) {
// ELSE BLOCK
}
Variants:
for (const key in myObj) {
} else {
}
for (let i = 0; i < x; i++) {
} else {
}
let i;
while (i < x) {
i++;
} else {
}
let i;
do {
i++;
} while (i < x) else {
}
We already have the bitwise ^ (xor) operator,
What about a boolean XOR operator?
true XOR false // => true
false XOR true // => true
false XOR false // => false
true XOR true // => false
Note, that operator would behave basically like !==
when used exclusively on booleans
true !== false // => true
false !== true // => true
false !== false // => false
true !== true // => false
But would have a different behavior on other types
0 !== '' // => true
0 XOR '' // => false (because Boolean(0) XOR Boolean('') is false XOR false)
Actually after some thinking I realised it would then be the same as the coercing !=
0 != '' // => false
0 XOR '' // => false
We already have Date#toISOString()
, but we often only need the date part of it, or the time part of it (mostly for the DOM datetime/datetime-local/time input types)
Whereas Date#toISOString()
outputs the date with time and timezone (2017-11-29T10:33:08.708Z
)
Date#toISODateString()
would only output the date (2017-11-29
)Date#toISOTimeString()
would only output the time (10:33:08.708
)Date#toISODateTimeString()
would output the date and time without the timezone (2017-11-29T10:33:08.708
)
Alternatively we could add an option object parameter to Date#toISOString()
:
Date#toISOString({
time: true,
date: false,
timezone: false,
})
There is a convention in node to put the callback as the final parameter. This is currenctly incompatible with the rest parameter as that parameter needs to be the final parameter.
// this is obviously a dummy example
function arrayify(...data, cb) {
cb(data);
}
arrayify(1, 2, 3, (data) => {
console.log(data); // outputs [1, 2, 3]
});
There is currently no way to achieve this without using arguments
.
Same as above, for iterable destructuring:
const [a, ...b, c] = [1, 2, 3, 4, 5]; // a = 1, b = [2,3,4], c = 5
const [a, ...b, c] = [1, 2, 3]; // a = 1, b = [2], c = 3
const [a, ...b, c] = [1, 2]; // a = 1, b = [], c = 2
const [a, ...b, c] = [1]; // a = 1, b = [], c = undefined
const [a, ...b, c] = []; // a = undefined, b = [], c = undefined
How it could work: https://gist.github.com/Ephys/2c7cd3a2afdea4e3ad2bc7910f4d3281
Examples:
// await Promise.all([...])
await.all [
getPost(7);
getPostComments(7);
];
// await Promise.race([...])
await.race [
getPost(7);
getPostComments(7);
];
// await Promise.reject(error)
await.reject error; // = throw error;
// await Promise.resolve(7)
await.resolve 7; // = await 7;
// as it's just an alias, it also works with any method added to the Promise object.
Promise.resolve7 = function resolve7() { return Promise.resolve(7); }
await.resolve7; // => 7
I know this is never going to happen.
new Array(number)
creates an array with its length
property set to number
, whereas new Array
with a non-number first parameter or more than one parameter creates an Array
containing the constructor arguments.
Given that new Array(number)
fills the array with empty items, that behavior should be removed.
Note: This has been mostly fixed using Array.from
(Not happening https://esdiscuss.org/topic/typeof-null)
Typeof should return the name of the primitive type of the object.
// currently:
typeof 5 // number
typeof 'hello' // string
typeof Symbol('Hello') // symbol
typeof false // boolean
typeof null // object
typeof void 0 // undefined
typeof {} // object
typeof [] // object
typeof function () {} // function
// With 'use typeof';
// returns the actual type
typeof 5 // number
typeof 'hello' // string
typeof Symbol('Hello') // symbol
typeof false // boolean
typeof null // null
typeof void 0 // undefined
typeof {} // object
typeof [] // object
typeof function () {} // object