New Features in ES2019
javascript.christmas
javascript.christmas
One thing that I am a bit afraid about though is that the language might become more complex and complicated over time because of backward compatibility (C++-like?). I saw that they will be introducing a new function "replaceAll(...)" to avoid the weird semantics of "replace(...)" (stopping after first occurrence unless a 'global' RegExp is passed as argument). And that's great, but that's a new function, and maybe 'replace(...)' should have had this behavior from the start. I hope to be wrong on this one!
New methods with simple behaviour don't really make the language more complex through.
> And that's great, but that's a new function, and maybe 'replace(...)' should have had this behavior from the start.
As you note it kinda does, just in a weird roundabout manner.
One issue with javascript is also that it makes extending existing functions difficult, because extra arguments are just ignored by default, so you can't just add a `count=1` parameter to String.replace to override the regex flag as there could be code out there which already passes a third (currently ignored) parameter and would break.
I think they do, in some ways. If you have multiple methods or functions which seem to do similar things, as a new-comer it can be incredibly confusing. I remember when I started I never knew what the "best way" to iterate on things was... for loops? for in? for of? foreach? And it was not obvious which one to use or what were the differences. I agree that it might be less ambiguous between "replace" and "replaceAll", but I think over time these things matter.
Possibly aside from the latter (assuming you mean Array#forEach), these are not different "methods with simple behaviour", they're different language constructs with largely overlapping behaviour & use cases.
The last added one is slice, and actually simplifies things, with its consistency with Array.prototype.slice, and replace the old ones
I agree it's annoying to keep old methods for backward-comptibility
For example, "move semantics" in C++ -- while useful -- trigger tons of questions about interactions with other features.
Duplicate features (for-of/forEach) are a problem, but a lesser one.
---
FWIW,
* Array.prototype.forEach iterates over an array
* for-of iterates over an iterable, including arrays
* for-in iterates over object keys (strings)
To your point, for-of is the more general form of forEach, so IMO there isn't much reason to use it now.
Ruby excels at this. There are 5 ways to do everything, but they're all simple and readable.
C++ doesn't actually need it. Rust proved that capture everything by reference or capture everything by move is enough for all practical use cases.
C++ lambdas is another example where C++ committee chose complex uber-universal solution instead of much simpler which solves 99% use cases.
C++ object lifetime management is much more tricky and error prone than the Rust one.
Passing an object by reference to lambda by default can be a beautiful source of out of scope and default.
The committee did an excellent job by allowing explicit filtering.
That's kinda true but kinda not: precise "capture clauses" is a common design pattern in rust[0] showing that the flexibility is extremely useful, and I think I've seen rumblings about adding them to the language.
I mean technically you only ever need `move` closure, "ref" closures are already a convenience.
[0] http://smallcultfollowing.com/babysteps/blog/2018/04/24/rust...
If you did have a JSON date, how would you decide what it was? Would it be a timestamp, or a civil date-time? Would it have timezones? Offsets? Locations? Would there be a database along with it required to understand it correctly?
Just stick with ISO8601 strings.
You could make the same argument with numbers - there's no reason you couldn't just pass strings containing the number - but there's advantages to being able to distinguish between a string and a number when the schema is unknown.
The reason JSON doesn't have dates is simply because JavaScript doesn't have date literals. You can write new Date(...) in JavaScript, but allowing that in JSON would have opened a whole can of worms.
Why not "just stick with" decimal strings?
Is this a joke?
JSON numbers are....I don't know what they even are. JavaScript -- the originator of JSON -- can't support 64-bit integers in JSON. Nor can it support decimal fractions in JSON, despite JSON using decimals. And yet JSON numbers also aren'ty IEEE-754 floating point numbers, as they lack +/-Infinity and NaN.
JSON numbers are ill-defined and unique from every other "number" concept in any language or format.
If you mean a 64-bit integer type (which, to be fair, is 11 bits larger than what JavaScript supports), then that's still not enough. As long as you want nanosecond-level granularity, and range beyond the current decade for your timestamps, you'll need more than 8 bytes to represent them in code.
And if you're deep enough to worry about the cost of those extra bits, you probably want to have your own date type anyway, tuned for your exact problem.
Also, forget about the receiving side, you're also assuming that the sending side meant the date for the instant as at offset 0 instead of in their own timezone (which is probably how the instant is being displayed for them). From experience, that's not a safe assumption - witness any instant meant to mean "today's date here and now" derived from a now() call alone or truncated to have 0 time parts (in current time zone!) - I see that attempted a lot. Way too error prone especially when you don't control both sides.
If you're using code that doesn't know how to handle UTC or converts it automatically to some server/env timezone, or doesn't check the timezone of the dates then that's a problem with that code, and has nothing to do with JSON or unix time.
The confusion might be coming from JavaScript incorrectly calling its instant representation Date, when it is actually quite a bit more precise than that.
Its also worth noting that you can't pick a time like 12:00 UTC and have it retain the calendar day in all time zones, because there are a few stray islands which are UTC+14 rather than UTC-10.
const res = await fetch(url);
const data = await res.json();
For posting you do need JSON.stringify(data), but I never thought of it as painful, just a single line.It does not do it automatically. Even in your example, you call `Response.json()` to do the parsing. If you change that to `Response.text()` it will be text instead. If you don't include the `.json()` call, nothing will be parsed from the body.
> Now the function would rather insert an escape character before the character code so that the result is still readable and valid UTF-8/UTF-16 code:
> JSON.stringify('\uD83D');
> // '"\\ud83d"'
I’m inclined to consider this a misfeature, unbreaking something that I’m glad was broken and should have remained broken.
Unpaired surrogates are (to simplify terminology a little) basically invalid Unicode. On the web platform, the only situation where unpaired surrogates should be encountered is as a transient state on user input, on platforms that send non-BMP characters through in two pieces (which is most browsers on Windows). Beyond that, nothing should ever deal in unpaired surrogates, because they will make things blow up and your life miserable.
Unpaired surrogates cannot be represented in UTF-8, or in well-formed UTF-16 (and that’s where JavaScript did the annoyingly bad thing that we’re paying for decades later, going with UTF-16 and not requiring well-formedness). Hence I’d quibble with the description of the result as “valid UTF-8/UTF-16 code”. (Sure, the actual JSON byte stream will be valid, but it contains an escaped string that is not valid.) U+FFFD REPLACEMENT CHARACTER was at least as valid, arguably more valid.
Various JSON parsers will choke on such strings, as observed in what I’d consider the canonical JSON spec: https://tools.ietf.org/html/rfc7159#section-8.2.
In the I-JSON restricted subset, which is what I’d say everything should actually limit itself to, such strings are disallowed and will cause parse failure: https://tools.ietf.org/html/rfc7493#section-2.1.
That is, formerly U+D83D would pass through straight as U+D83D (or possibly be replaced by U+FFFD in some implementations), whereas it is now "encoded" as the sequence U+005C U+0075 U+0044 U+0038 U+0033 U+0044.
That is, the entire point of the "well-formed stringify" proposal[1] was to stop generating broken JSON:
> Rather than return unpaired surrogate code points as single UTF-16 code units, represent them with JSON escape sequences.
[0] it's possible that some implementations would replace the lone surrogate by U+FFFD but according to MDN:
> Before this change JSON.stringify would output lone surrogates if the input contained any lone surrogates; such strings could not be encoded in valid UTF-8 or UTF-16
this is also the behaviour I observe on an old safari, and the one which is quoted in the tc39 proposal[1]:
> JSON.stringify can return strings including code points that have no representation in UTF-8 (specifically, surrogate code points U+D800 through U+DFFF). And contrary to the description of JSON.stringify, such strings are not "in UTF-16" because "isolated UTF-16 code units in the range D800₁₆..DFFF₁₆ are ill-formed" per The Unicode Standard, Version 10.0.0, Section 3.4 at definition D91 and excluded from being "in UTF-16" per definition D89.
The article does contain an error here, for the code point on the page is actually U+FFFD, not U+D83D (which of course is unrepresentable there). I trusted that (being a little surprised that it’d do the replacement, which is more what I’d expect of Python, but accepting it all the same) and didn’t pull out an old browser to confirm what actually happens, which is, as you say, an unpaired surrogate code point.
The article should probably say '"\ud83d"' instead of '"�"' (even though the number of backslashes still then requires thought), because yielding U+FFFD there would be a completely valid (and preferable, in my opinion) solution.
The MDN page also does that, that's a bit misleading but TFA commits way worse a sin: it actually states the unpaired codepoint is swapped for the replacement character:
> In earlier versions, these would be replaced with a special character:
which is just wrong.
> yielding U+FFFD there would be a completely valid (and preferable, in my opinion) solution.
I agree. The proposal does not explain whether U+FFFD was considered, I expect they picked the escaped version to limit or avoid data loss: if you get an U+FFFD you have no idea what it used to be.
Eh... that's great and all, but why not go the whole way and allow me to just use `try { }` without a catch-block? I'm sure that was part of a conversation somewhere, and I wonder why they chose not to go that far.
try foo() with | ex1 -> failWith "doh!"
and allowing these to be expressions rather than just statements. Exceptions become much easier to deal with this way: let result = try compute() with ex1 -> failwith "doh!" | ex2 -> -1 try {throw new Error('')} // exception is caught
try {throw new Error('')} // uncaught exception
finally {console.log('finally')}
That would be a bit of a footgun, as adding a finally clause that does something innocuous like logging would result in an uncaught exception!It seems like this code in Ruby:
```
begin
do_something
rescue => e handle_error e
ensure log_something
end```
is equivalent to this in JavaScript:
```
try {
doSomething();
} catch(e) { handleError(e);
} finally { logSomething();
}```
I'm not saying that they're 100% equivalent, but I was thinking of the `try` block as if it's `rescue` when really it has more in common with `begin` in Ruby in that it's defining a block of code that can be rescued/caught if an error occurs.
So I guess my confusion comes from this type of block being named `try` rather than something like `do` or `begin`.
TL;DR I was just thinking about this in the wrong way from what I can tell.
EDIT: Yet I think that my point might still stand in that, if `try` is just a block that has its own scope, like an if-block or `begin` in Ruby, then it should be possible to not require either `catch` or `finally` since its own behavior has little to do with error handling.
For instance, this is possible:
```
let x = 'foo';
try {
let x = 'bar';
} catch(err) { // noop
}console.log(x); // foo
```
The try-block would be way more useful if it could just define scope without being tied specifically to error handling. There are lots of circumstances where defining scope would be handy outside of conditionals and creating functions for a similar purpose.
I guess what might have made the most sense, if JS could have started over, is that there'd be no `try` statement and that `do` could be used in its place.
```
const words = ['foo', 'bar', 'baz'];
let i = 0;
do {
console.log(words[i]);
i++;
} catch(err) { console.error('whoops!');
} while (i < words.length);```
We can't go back now since JS, for good reasons, is remaining mostly backwards-compatible. But the ability to do the above without extra gymnastics would be pretty cool.
This is the case today! With `let` and `const`, any {} block creates a scope. So:
let foo = 'outside';
{
let foo = 'inside';
console.log(foo); // logs 'inside'
}
console.log(foo); // logs 'outside'Like I wonder how many bugs were introduced because the developer didn’t realize Array.sort() was unstable.
[1,2,10].sort()
= [1,10,2]Hell, really?? That's gross.
As for the parent's comment about a dev "not realising array.sort() was unstable", the dev should know his tools. I can't see anyway why a stable sort is better, I've never sorted on x then sorted on x, you just sort on x & y.
It's just using the == operator, no? Which, yes, is really gross.
If the comparison function argument was mandatory in sort() this wouldn’t be a problem.
Stupid defaults is not "the curse of optional arguments", it's the curse of stupid defaults.
Most languages have defaults for these two operations yet have proper defaults rather than stupid ones. Python's list.sort() and int() certainly do, so do Java's Collections.sort() and Integer.parseInt().
Uniquely awful defaults is a historical (and defining) feature of javascript, not of having default values, or optional arguments.
[1,2,10].map(parseInt)
It’s several things that on their own aren’t mad which taken together produces a crazy result. 10 as the default for an optional second argument of parseInt is fine. But map should only use a single argument closure by default. So map(parseInt) must be equivalent to map(x -> parseInt(x)).
(a, b) -> a.toString().compareTo(b.toString())
Unlike say int.parse() where 10 is a reasonable default for radix, doing string comparison as default is almost never what the caller wants, so is a bad default. Functions where there is no obvious (99% of the time the desired value) should not have a default value when the argument is omitted. I’d much rather have things.sort() just fail and force me to specify the comparison, than to see it sort alphabetically.
As a side note: a second optional argument specifying stability true/false would be reasonable. True could be the default too, since unstable sorting is effectively an optimization with a tradeoff.
When neither the function nor the parameters have hard types, you have to create heuristics. There could be a test for numbers there, but it would also be surprising because at the older days people expected "10" and 10 to behave the same.
[1, 10, 2].sort((a,b) => a > b ? 1 : -1)
// -> [1, 2, 10]
is something like // [1, 10, 2].sort((a,b) => a.toString() > b.toString() ? 1 : -1)
// -> [1, 10, 2]
because someone thought that it was "best" to cast stuff to string in case whatever you put in the container didn't implement comparison.A JavaScript sort comparator is required to not only return a positive or negative value, it must handle all three cases: greater than, less than, or equal. When the arguments compare equal, the comparator must return 0.
Returning only a positive or negative value is a very common error. (I don't mean to pick on you for this! I have seen the error so often that I hope this may be helpful for anyone writing a sort comparator.)
The invalid comparator will force the normally stable sort to be unstable, because sort() now has no way to know that two items should be considered equal and their existing order preserved.
Let's demonstrate with the "doggos" example that the article links to:
https://mathiasbynens.be/demo/sort-stability
The code starts with this array:
const doggos = [
{ name: 'Choco', rating: 12 },
{ name: 'Devlin', rating: 13 },
{ name: 'Eagle', rating: 13 },
{ name: 'Jenny', rating: 13 },
{ name: 'Kona', rating: 13 },
{ name: 'Leila', rating: 13 },
{ name: 'Milly', rating: 14 },
{ name: 'Molly', rating: 12 },
{ name: 'Nova', rating: 12 },
{ name: 'Oliver', rating: 13 },
{ name: 'Patches', rating: 14 },
];
It uses this valid comparator: doggos.sort( ( a, b ) => b.rating - a.rating );
(Note that this implements a reverse sort as can be seen in the output on the test page.)Subtraction provides the proper return values when two numbers are compared: any negative number, any positive number, or zero when the ratings are equal.
Another valid comparator (again using a reverse sort) would be:
doggos.sort( ( a, b ) =>
a.rating < b.rating ? 1 :
a.rating > b.rating ? -1 :
0
);
Either way you get the expected output in the current version of Chrome, which has a stable sort. Each group of doggos with the same rating appear in their original order: [
{ name: "Milly", rating: 14 },
{ name: "Patches", rating: 14 },
{ name: "Devlin", rating: 13 },
{ name: "Eagle", rating: 13 },
{ name: "Jenny", rating: 13 },
{ name: "Kona", rating: 13 },
{ name: "Leila", rating: 13 },
{ name: "Oliver", rating: 13 },
{ name: "Choco", rating: 12 },
{ name: "Molly", rating: 12 },
{ name: "Nova", rating: 12 }
]
So let's go back to the original doggos array and use a sort function that only returns 1 or -1 (once again using a reverse sort): doggos.sort( ( a, b ) =>
a.rating < b.rating ? 1 : -1
);
With this function, the result is: [
{ "name": "Patches", "rating": 14 },
{ "name": "Milly", "rating": 14 },
{ "name": "Oliver", "rating": 13 },
{ "name": "Leila", "rating": 13 },
{ "name": "Kona", "rating": 13 },
{ "name": "Jenny", "rating": 13 },
{ "name": "Eagle", "rating": 13 },
{ "name": "Devlin", "rating": 13 },
{ "name": "Nova", "rating": 12 },
{ "name": "Molly", "rating": 12 },
{ "name": "Choco", "rating": 12 }
]
Now the sort has become unstable. Doggos with the same rating are no longer in their original order.Bottom line, be sure to handle all three cases in your sort comparator if you want a stable sort.
A good alternative for statically typed languages is to have the comparison result be a proper sum type, and provide ways to generate and compose comparisons e.g. Haskell or Rust's Ordering (and Ord trait / typeclass).
You seem to think stable sort is much more important than most of the rest of the field.
By default, C sort isn't stable, C++ sort isn't stable, Ruby sort isn't stable, C# sort isn't stable, Perl sort isn't stable. Hell, not even GNU's sort utility is stable.
JavaScript has some unexpected idiosyncracies, but sort() stability isn't one of them.
For example `Object.fromEntries(new URLSearchParams(window.location.search))` will parse a query string into a nice JS object. This works because the iterator of the search params class returns tuples.
What is already available today is `new URLSearchParams(Object.entries(params))` for turning a "params" object into a query string.
I don't follow what it would use function strings for determining missing - presumably - polyfills that you couldn't do in better ways.
myApp.controller('MyCtrl', function($myCoolService) {
//...
});
Angular would see that you are looking for a parameter named myCoolService, see if it already has it, and then inject it when calling your controller.This of course broke in various ways with minification when the parameter names were mangled. So one had the opportunity to use "array syntax", requesting a dependency by a string. Or use a preprocessor in the build script adding the array syntax before minifying.
I wonder if Java will get pattern matching before JS https://medium.com/better-programming/top-5-new-features-exp...
Not sure how how much backwards compatibility this would break, but at some point we must allow for newer better less error-prone versions of the language to co-exist in different modules. That is not difficult at all. Consider the case of "use strict"; which does just such a thing. We could have "use strict 2020" etc.
Trying to retroactively fixing bugs in code that did not follow the standard is not a good idea IMHO.
It was actually proposed by the v8 / chrome people (https://v8.dev/features/stable-sort) after they'd finally come around to implement one of the oldest v8 feature requests: https://bugs.chromium.org/p/v8/issues/detail?id=90
And stable does make for a better default than unstable: it offers stronger guarantees and more reliable behaviour. That's doubly important because by and large developers work to the implementation not the spec. And it's unlikely you'll ever change that.
The last one to switch was Chakra (Edge), and that one was weird as it used a stable sort up to 512 elements, and unstable above.
Funnily enough, Mozilla had originally switched to a stable sort because MSIE used a stable sort: https://bugzilla.mozilla.org/show_bug.cgi?id=224128
(An alternative to String.prototype.trimStart would be to use String.prototype.replace, although trimStart would probably be more efficient.) Still some things missing includes: a goto command, ability to disable automatic semicolon insertion, macros, enhanced regular expressions, and a built-in regular expression quotation function (it is easily enough to implement in JavaScript, but it seem to me the kind of thing that should be built-in).
One thing I'd to request is that `Object.fromEntries()` simply skip undefined entries, rather than do...strange things. I wrote an `mapObject` function that looks like `(a,fn) => Object.fromEntries(Object.entries(a).map(fn))` and occasionally the fn needs to skip an entry and the easiest way to do this is just return `undefined`.
Not sure what strange things it does. fromEntries simply takes each entry, sets its first item as key (stringifying if necessary as object keys are necessarily strings) and the second item as value. Naturally falls from these that a null or undefined entry will error, and an empty entry will create an {undefined: undefined} item.
> I wrote an `mapObject` function that looks like `(a,fn) => Object.fromEntries(Object.entries(a).map(fn))` and occasionally the fn needs to skip an entry and the easiest way to do this is just return `undefined`.
Use flatMap instead?
``` let p = (a,fn) => Object.fromEntries(Object.entries(a).map(fn)) p({a:1, b:2}, ([k,v])=> v === 2 ? undefined : [k,v]) ```
My intent is to skip the entry with `b:2`. However, both map and flatMap error out. It would be nice if `fromEntries` did what I think is the appropriate thing, which is just skip undefined and null. Is this not the behavior you'd expect?
Note that without special treatment in fromEntries, there's no way for the mapping function to indicate "skip this entry". You have to do it in another pass, or some other way.
It's absolutely applicable, flatMap can trivially act as a filterMap by returning an empty array in the "remove this element" case:
let p = (a,fn) => Object.fromEntries(Object.entries(a).flatMap(fn))
p({a:1, b:2}, ([k,v])=> v === 2 ? [] : [[k,v]])
there you go.You can even make the adaptation transparent by wrapping the fn:
let p = (a,fn) => Object.fromEntries(Object.entries(a).flatMap((v) => {
let r = fn(v);
return r == null ? [] : [r];
}));
p({a:1, b:2}, ([k,v])=> v === 2 ? undefined : [k,v])
> My intent is to skip the entry with `b:2`. However, both map and flatMap error out.Return an empty list from flatmap to remove the entry and a singleton list containing the new pair to alter it.
> Is this not the behavior you'd expect?
No. I would much rather have the existing function which has a very clear and straightforward behaviour and is not prone to silently misbehaving on buggy code.
> Note that without special treatment in fromEntries, there's no way for the mapping function to indicate "skip this entry".
I fail to see an issue with that. If you want to remove an entry, remove the entry.
But overall, loving the direction JS is taking.
If only it was that easy. The weeks I’ve spent hunting down webpack specific bugs because the plugins don’t always quite work with typescript transformations and source maps...
There are many options for source maps due to their performance overhead and the docs describe then in detail and provide a guide for when to use each one.
So this is basically a fantasy what javascript could be no different than any other alt-js languages .
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
If people are sticking to an older browser, they will never have the functionality (this can somewhat be mitigated through transpiling using Babel, but this isn't foolproof).
It often takes years for a feature to be widely deployed enough that no tranpiling is required. There are still features from ES2015 that I think still require transpiling.