ES6 in Depth: Destructuring
hacks.mozilla.org
hacks.mozilla.org
With our Flux implementation that we're using[0], async actions to external services become amazingly simple.
async sendRequest(payload) {
try {
let result = await this.api.sendRequest(payload);
return await result;
} catch (e) {
if (e.name === 'ServerException') {
this.errorActions.serverError(e);
return null;
}
this.errorActions.genericError(e);
return null;
}
}
Having a class system on top of prototype system, removing a lot of the boilerplate is great. Browserify/CommonJS and Babel make for a phenomenal build system, and being able to with few exceptions render everything on the server correctly is brilliant.Javascript has come a long way. The best part of destructuring is simplifying `import` statements
import { fooFunc } from './Bar';
I highly recommend checking it out, as the confluence of ES6/7, Babel, React and Flux (with one way data-flow) feels like the future but here today. That, and I'm stoked that functional programming concepts are taking off!To summarize, the code looks neat. But debugging a non-trivial app still has some way to go.
With it, I can unit test my actions and other async modules in complete isolation, so the debugging burden is lessened. Id love to be able to remove console.trace() calls in my async code in the error handling side, and I think we will get there soon, but things can be worked around and the control flow becomes far easier to understand than using generators by hand, or callbacks.
IMO, control flow has no advantages over generators. It is exactly the same thing, replace await with yield/yield-star. Btw, until aysnc debugging becomes better, replacing yield with delegating yield even gives you stack traces since the delegation is handled by the runtime (and control does not pass back to the function (like Q.async, co, spawn) that's driving generators).
//From my current project
static *getInitialProps(name) {
var project = yield* Project.getByName(name);
return { project };
}
async is a better way to do things, I agree. In fact, I asked this silly question once on es-discuss and got educated. https://mail.mozilla.org/pipermail/es-discuss/2014-September...You're right, in that it makes little difference in terms of semantics per se, but having implemented both in the application we're working on (which is at just under 10k semicolons, currently) async/await along with how Flummox/Flux implements the dispatcher, it allows you to work with async methods without propgating async through the entire call stack, and lets you think of the runtime as being synchronous-ish. I'm looking forward to finishing this project, as there's a tonne of stuff I want to write up about it; I think the big issue with ES6/7 is that there isn't a lot of "in the trenches" writeups.
Thanks so much for the discussion on esdiscuss by the way, it really helped me understand async execution and control flow in ES6/7.
PS. This is a neat project, HTTP servers with ES7 async/await. Similar in concept to Koa, but using the newer syntax! https://github.com/quinnjs/quinn
It will transforms async functions to a generator, with a far better debugging experience, and generators are supported in chrome dev and firefox now.
The developer of Alt (http://alt.js.org), Josh, is currently working on some documentation that will be exactly what you're after -- head over to the Reactiflux community on Slack, and go check out Alt's GitHub as I believe it's being put up there!
EDIT: Oh, and I highly recommend going through this code: https://github.com/goatslacker/microflux -- it will really make how Flux works clear. Most "modern" (haha, Flux itself is what 12 months old?) Flux implementations are pretty simple to be honest, so their code is often the best place to look to see how things are supposed to work.
import { fooFunc as otherFooFunc} from './Bar';
not import { fooFunc: otherFooFunc} from './Bar';Import is another concept that will eventually make your code base unable to manage. You'll end up with imports everywhere, with a weird spaghetti, where it would be much better to use the modular pattern of breaking up the code in separate reusable modules.
As to the second part of your comment regarding imports... import really isn't any different than require, and in that vein is easy enough to reason against, and work through. For the most part, your discrete modules should be hierarchical in nature, and exposed as collections/wrappers via directory/index structures... this will make it easier to avoid spaghetti.
Classes aren't great either, but at least using class inheritance makes you feel icky, you're not likely to have shared state on the prototype being randomly mutated, and it's pretty readable.
Don't get me wrong, destructuring is a nifty feature, but it's not really necessary. Plus, things like that usually get thought through along with everything else, when the language is designed. Not only is this increasing the conceptual weight of the language (OK, maybe I exaggerate in this example ;) but there are many more features added all the time), but now you also have a new set of potential pitfalls when doing potentially type-inappropriate destructuring. (I see from the text that you sometimes get undefined, and sometimes a TypeError?) Does JS need more of that?
Why not keep it simple? It may have its flaws, but the JavaScript mental model, once you figure out some corner cases, is really simple and powerful. I find it sometimes very elegant. This feature bloat reduces that, IMO, and could hurt understandability of JS code.
Javascript was born with enormous defects that it's too late to remove, and adding new features, regardless of their undoubtable usefulness, make the whole language more difficult to handle. The number of concepts required to read ES6 is far more than the one required for ES5, and this number is just destined to get higher and higher, with no possibility to decrease.
I know that Automatic Semicolon Insertion, Hoisting, Scopes and mostly automatic type casting are brought up a lot...
Regarding ASE (use a linter, and always require them)
For Hoisting it's a matter of learning the language and isn't any different than many other languages in that regard.
As to functional closures/scoping... the new "let" allows you to move away from that in practice
For automatic type inferrence/casting... I think this is absolutely one of the more powerful features of JS, and allows for validating end-user input to be far more easy/flexible than most other systems that would presume to convert user input to a date, or a number, etc. This is my own opinion, but I've seen few better systems and JS doesn't require you to load up a bunch of try/catch blocks to do it.
I bring these up, because I don't think they are defects in the language, except maybe the only closures being at the function level, and related to this being able to use undeclared variables, both of which are addressed in practice by newer features.
Of course, the corollary is to point out that nor should you force your approach on others. Your usage of the language is not the same as everybody else's. They might be working in different contexts to you, solving different kinds of problems to you, with different constraints.
I'd also recommend subscribing to es-discuss. All the features are discussed heavily and repeatedly by experienced, clever people in an open forum, before being standardised and implemented. Only battle-hardened ideas, usually proven in other languages or in JS libraries, make it through the process. There's solid reasoning behind all of them.
Thanks for the es-discuss suggestion, it's cool that the process is open like that.
But it is always a useful process to go through, more so when it exposes you to new ideas, patterns and idioms. These features seem intimidating because they are unfamiliar to you, but they won't be unfamiliar forever. I guarantee it.
Strict backwards compatibility forcing new syntax and ugly conventions.
> I really do not like this constant stream of new features in JavaScript
ES6/Ecmascript Harmony was announced seven years back, and has been actively discussed for the last four years. So the constant stream of new features you see are JS engines implementing parts of the standard, which is now in its final shape. ES-Discuss mailing lists* are open for community participation (like someone mentioned below) and is led by people with a lot of experience in their respective focus areas. Every new feature has had to pass through many levels of debates, and have (mostly) been borrowed after being found successful either in other languages or libraries.
> I feel like these new features are bloating it, and for what?
De-structuring is fairly easy to grasp, and is not even one of the main draws in ES6. Since the last changes to JS, the environment and therefore expectations have changed a lot: (a) JavaScript is now a server-side technology in a big way, (b) async has become increasingly important, (c) people are writing very complex apps in JS. Without the changes in ES6 (and ES7/8 going forward), developers would have hated working with JS and JavaScript could have slowly died.
If you have been following, you'd notice that JS has a very vibrant community around it. One big reason for that is that they see the language evolving with the technology that surrounds it. Finally, note that all the new enhancements have come without breaking backward compatibility.
* I lurk there, not a contributor. Have learnt a ton from just subscribing to it.
I do think you might be breaking the guidelines of the site - downvote should not be used just for disagreement, or? - but who cares, you're certainly not the only one ;) In any case, thank you for the explanation! I appreciate it.
As for the rest, I partly agree. I am too late to voice any constructive feedback. It was just a general comment on the direction things seem to be going. And I'd rather not participate in the es-discuss, because JavaScript is not my professional focus any more, so I do not have the time or the required expertise. I understand that my comment seems kind of like taking a passing shit on someone's hard work, I'm sorry about that. I hope everyone takes it as nothing more than what it is - just an opinion ;)
All in all, I feel the language is getting overstretched and overcomplicated. And when I see destructuring, I wonder if it's really worth it. Too much complexity can be a problem.
Well, it's not mentioned in the guidelines, and there's a bunch of people who do use downvote to disagree. There's a few posts from PG; one where he says that people upvote to agree so it's understandable that they downvote to disagree, and others where he sees it as a problem to be addressed.
It's probably a good idea for people to upvote any posts they think have been unfairly downvoted. This sometimes happens, but probably not often enough.
1. a downvote button, which is available to everyone, but affects only the ranking on the comment list (no graying out due to too many downvotes)
2. a "flag" button, which is available to people with higher karma and serves for moderating.
...because, it seems perfectly human to want to express disagreement with a downvote. I can totally get it, and would/will probably have a hard time restraining myself ^^
I should exercise more restraint next time. Thanks for your reply.
Doesn't string interpolation break from ES5?
"${foo}" means something different in 6 vs 5, right?
let a = `${foo}`; //Template string
var b = "${foo}"; //same as in 5.And stuff like the module system, class system, promises etc. really needed to be standardized because there were too many different solutions floating around and the fragmentation was hurting the ecosystem.
Personally, I think that ES6 is much more usable than any previous version of JavaScript.
Second, stagnation is not a good thing. I make my living writing Javascript, and being able to write ES6 instead of ES5 makes me happier and more productive. That's not a minor benefit; that's literally the most important thing you can say about a language update. And thanks to babel, it doesn't even break backwards compatability. What possible drawback is there?
Yes, this is ruining the "funny little language" you liked, but if you want a funny toy language, go use Elm or Elixir or something. Javascript is too important to be a hipster playground.
My last experiments caused lots of browser based problems (string.prototype.contains doesn't exist in IE9 etc..) and ES6 features were a no-go for the same reason. How do you actually use things like the fat arrow etc. in applications today?
Is that limited to server-side code for now (and for quite some time..)? Do you decide to ignore a number of ~relevant~ browsers? Build tools to generate a 'lower' dialect/subset?
Any intro to this specific topic, i.e. 'making sure your ~modern~ code works in last years browsers'?
I actually do a lot ov server-side code in JS (via node/io.js) as well, so the build step isn't so bad.... breaking your code up into separate modules that build separately is a good idea though, so your build times aren't too much.
[1] https://github.com/paulmillr/es6-shim/ [2] https://babeljs.io/
It's a bit of work to get a project up and running from scratch, but there's plenty of templates, and the result is nice.
let myThing = {a: 4};
let {a} = myThing;
But this does not: let a = 5;
let myThing = {a: 4};
{a} = 5;
I understand there are some problems of ambiguity here, but it seems to me this could be made to work somehow? {
a
}
= 5
As in, a block with just an "a" in it, followed by an assignment to nothing, which throws an syntax error.You can however do
({a} = 5);
to make the parser switch to expecting a destructuring pattern instead of an block statement.But regardless, the corrected version of your example: `let a; ({a} = {a:5});` works like a charm and is very handy! Thanks for the tip!
[0] https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node252.html
a, b = [1, 2]
a, b = {:a => 1, :b => 2}
Similarly in Python, at least for the array example (there's probably some way of destructuring a dictionary in Python, but I don't know it myself).PHP also has `list()`...
[1] pry ~ » a, b = {a: 1, b: 2}
{
:a => 1,
:b => 2
}
[2] pry ~ » a
{
:a => 1,
:b => 2
}
[3] pry ~ » b
nil irb(main):005:0> a, b = {:a => 1, :b => 2}.values_at(:a, :b)
=> [1, 2]
Or: irb(main):006:0> a, b = (h = {:a => 1, :b => 2}).values_at(*h.keys)
=> [1, 2]
Edit: Ruby's hashes are ordered since 1.9, incidentally.I ask because I am genuinely interested, and haven't looked into it for a while, only passively in the context of Angular 2. The addition of attributes (@ tagging) is specifically interesting to me as I think it's a cleaner syntax visually than mounting methods onto a function after declaration.
With BabelJS, I can use flow to get type annotations, but not sure about meta annotations (like attribute declarations in .net)
[1] http://en.wikipedia.org/wiki/Destructor_%28computer_programm...
As someone else also mentioned in the comments, it is unapologetically dynamic. In many ways, this reminds me of Perl. The array destructuring assignment is such a common pattern in that language (eg. http://perldoc.perl.org/functions/caller.html)
What's even more exciting/terrifying is that ES6 goes even beyond the patterns allowed in Perl!
ES6:
let [,,,,,,,,, tenth_item] = somearray;
Perl: my (undef x 9, $tenth_item) = @somearray;
The ES language designers got that wrong. You carefully have to count the number of commas, and use an invisible nothing in between. Instead they should have made it so that you assign to undefined, or perhaps null, or make up a new special identifier named _ (like it is used in some other languages) and then the assignment operation is smart enough to discard the value.This is not only better because now there is a visible thing to see and talk about, but also allows the comma operator to be much less restricted and frees the programmer to be more expressive. In Perl, multiple commas are collapsed similar like multiple whitespace collapses in HTML. The expression a,,,,,,b is identical to a,b – also it does not matter where in the expression the multiple commas are.
In ES5 and later multiple commas at the end are collapsed into one, but otherwise multiple commas are kept. This is lame because inconsistent.
let [tenth_item] = somearray.slice(9) let [,second,,fourth,,sixth]I don't know about you, but this sensibly gives a TypeError when I did it in my console on Firefox 38. Please let this article be more dated than my firefox browser, because making NaN auto-coerce into an iterator sounds incredibly stupid.
var temp = NaN;
var wtf = temp[0];
console.log(wtf); //undefined
What else would you expect it to be? JavaScript in general guards against throwing errors as much as possible... I'd much rather see this behavior in practice than having to put extra try/catch blocks everywhere... Not to mention what this would do to async code. var {wtf} = NaN;
would set wtf to undefined, for the reasons described in the article, since it would end up doing (NaN)["wtf"]. But trying to destructure NaN into an array should throw an exception.like php's list/map but with several levels.
those are just asking for bugs that are hard to spot even on code reviews.
The main thing that bothers me is that the default behaviour is too permissive.
let a = [1,2,3,4,5];
let [b] = a; // ok. b receives 1
The equivalent Python will fail because there are "too many values to unpack." /* I can write code independent of getCheapestSellers() impl */
var [bestPrice, secondBest] = getCheapestSellers("product_name"); [bestPrice, secondBest, _*] = getCheapestSellers('product_name')Add: To clarify, the error shows up at runtime in Python. So you're going to need tests anyway. Just as in JS.
Btw (unrelated), you can do the same thing in JS as well [a, b, ...rest] = [1, 2, 3, 4, 5]
Keep in mind this also comes up in cases like:
foo, _, bar = something()
In general you can't avoid doing something like it, why "optimize" for this one special case?Because it appears the programmer's assumptions have been violated at this point. There's a syntactical way for destructuring assignment of a variable-length list, and it wasn't used. Maybe the programmer was being lazy, or maybe the programmer's assumptions were violated.
How do you feel about
[a, b, c, d] = [1, 2]
? Should it throw?> it doesn't throw an error in many situations, where the most likely case would be to suppress it anyway, or when there's no point in throwing one...
PHP rightly gets lots of flak for attempting to lumber on after the programmer probably made a mistake. JavaScript isn't as bad, but it does hide a fair number of programmer mistakes. For small web apps where failure or incorrect data isn't a big deal, maybe hiding small bugs is the right thing. For large applications, hiding likely programmer laziness or minor bugs is not a good feature.
In terms of your example, in JS, I would expect c and d to be undefined. Though I sometimes wish that JS hadn't made the distinction between undefined as a value vs. null. However, the JS flow makes it easier to do something like...
let [foo, bar, baz, optional] = getConditionsFor(id);
Where you expect certain values to only sometimes be there conditionally... To me this is flexibility that I appreciate, and once you are used to the JS way it becomes a lot more natural.At this point I've written a lot of code in JS both on the server and the client... after 3 years of mostly node/js projects, I've spent the past week in .Net land and frankly I miss JS, though the JS on the web portion of this project is almost as painful. It's amazing how many times a small piece of coupled code can by copy/pasted instead of isolating it for reuse... Spending a week cleaning up such code to get a handle on a number of bugs, not fun.
Even worse:
let [a, b, c, d] = [2];
a receives 2. b, c, and d receive undefined.JavaScript is a good language because it's so simple and easy to learn, adding stuff like destructor's just make it more confusing! Making a language more confusing just because of strong preference with syntactic sugars is a mistake. I would like to see a forking of the language before this goes mainstream. Maybe called CJS, where C stands for Clean or Classic JavaScript.
let {title, content, email} = articles[0];
let [all, ident, domain] = email.match(/[^@]*@.*/);
return `<h1>${title} (${ident} at ${domain})</h1><p>${content}</p>`;
Secondly, there is no destructor in JS (because of dynamic memory management), and there will probably never be. Destructuring is nothing like destructors.Thirdly, the language doesn't have to fork. You can still write ES5 code. But you should probably not because, believe it or not, ES2015 is a really good thing, full of improvements. And syntaxic sugars are part of what makes using it so much nicer than ES5.
I can understand why you think the email example looks better, but whoops, there's a bug, you now got two undefined variables!
var email = article.email,
at = email.indexOf("@"),
ident = email.substr(0, at),
domain = email.substring(at + 1, email.lengt); articles.forEach(article => {
let { title, content, email } = article;
...
})
Might have made that more clear... but .forEach is an ES5 addition...Given the backlash with ES6, I wonder why we didn't see this with ES5... "OMG they added extra sugar to Array.prototype, we're all gonna die!"
And in order to do it you have to use something like Babel.
Why not just move to CoffeeScript, or IcedCoffeeScript, or ToffeeScript, or even LiveScript?
The only explanation I can see is that people just aren't capable of learning the full new languages and so the standards people are leading you by the hand like you just got off the short bus.
AND, the standards people don't want to admit that individuals did their jobs for them years ago, don't want to use someone else's design for implementing those features and so insist on slowly coming up with their own independent implementations for engines.
This is a microcosm that demonstrates the relationship between technology and society. There is just a huge lag between the newest and best ideas or systems and what the group or individuals or systems can grasp or absorb.
Take this existing many-year gap and multiply by 10X or 100X and you will start to perhaps understand the concept of the technological singularity.