ES6: The features I'm most excited about
justicen.com
justicen.com
Generally though, is it reasonable for features such as these to be included at the language level? I primarily use ES5 JavaScript so I'm used to pulling in a decent amount of modules (in this case handlebars, underscore, etc.) for things like templating.
But I will be relieved to have a language-standard solution to what feels like such a common problem for the environments in which JS is used primarily, despite the tradeoff that's made in accessibility due to feature bloat.
I'm excited for not having external libraries to handle string interpolation.
Unsafe string-munging is too easy and seductive. Doing things the safe/secure way needs to be at least as easy.
And it makes for a growable language, the way Lisp macros do. E.g. https://github.com/erights/quasiParserGenerator This can reduce the demand for feature bloat in future language standards.
Admittedly you could trivially extend the string prototype to have something more akin to string.format or printf, which was something you couldn't do in other languages for a long time, making it less of a problem.
In python you can save a string like: template = '%s is %s' and _then_ evaluate its output: print template % ('john', 'tall')
I've recently started a new project in React/Flux, and I've cut away 3 or 4 dependencies compared to previous projects by fully embracing ES2015/2016, as Babel automatically provides the necessary polyfills and compilation.
As for template strings, I think they make sense. Most programming languages have them in someway or another, like Ruby "hello #{world}" or Python 'Hello {world}'.format(world='World').
[1] https://www.reindex.io/blog/you-might-not-need-underscore/
var multi_line = 'You already \
can do this in ES5.';At least now with classes, users coming from other languages wont be as tempted to make their own completely incompatible object systems.
1. It will become the common interface for deferred operations and library authors can make assumptions that it's there.
2. ES7 Async/Await will be able to leverage this common interface.
But in the ES6 world, making your API promise-based makes it heavier-feeling and more awkward than simple, clean callbacks. (Which get an unnecessarily bad rap from people who don't work primarily in JS; there are cases when callbacks get awkward, but just looking at code that's four levels of indent deep and declaring it "callback hell" -- as you so frequently see -- is a super-shallow analysis. That code is clean and easy to understand, as often as not.)
// using arrow
var adder = {
num: 2,
nums: [1,2,3,4,5],
addIt() {
return this.nums.map(n => this.num + n)
}
};
console.log(adder.addIt()); // [3, 4, 5, 6, 7]
I'll concede that the arrow function is a bit overly complicated, with this, sometimes-optional argument list parenthesis, and object literal/function body syntax ambiguity. // myModule.js
export function myModule(someArg) {
return someArg;
}
// main.js
import myModule from 'myModule';
The import is importing a non-existent default export. Either the export needs to be changed to a default export: export default function myModule(someArg) {
-or- the import needs to be changed to importing a member: import {myModule} from 'myModule'; "use strict";
class Vehicle {
constructor(name) {
this.kind = 'Vehicle';
this.name = name;
}
printName() {
console.log(this.name);
}
}
class Car extends Vehicle {
constructor(name) {
super(name); //call the parent method with super
this.kind = 'Car';
}
}
let myCar = new Vehicle('Mercedes');
console.log(myCar);I would say that JavaScript's OO is actually more powerful/flexible than other languages but it is less readable and more error prone if you're not careful - For example, the fact that objects can 'borrow' a method from other object an apply it to themselves is a common source of issues for beginners.
Rust and Go are so exciting because they've intentionally rejected flexibility without compromising power.
One thing I wish JS had is a PEP8-style canonical style document. I would find that more useful than classes.
On classes, I am among the less excited about them; however, we are in a ridiculous situation where there are about 500 different library implementations of class inheritance. Backbone has one. Ember has one. Node has util.inherits. Various transpile-to-JS languages (CS, TS) all have their own slightly-different implementations of the resulting prototype code. There is plainly a need for classes in JS, felt by some of the leading projects in JS land.
ES6 classes at least gives all these disparate implementations a refactoring target. How many of them will get there is another matter.
That's bad. Few developers have self-discipline. The ones who do still have different rules than you do. That makes reading, understanding, and modifying their code harder. Sounds bad, doesn't it?
There's a reason the most experienced devs rage against extremely flexible languages like PHP and JavaScript and are excited about new, ultra-rigid languages.
Citation needed, or, I'm not quite sure I agree with you there. In fact, I think I believe the exact opposite (to a degree).
It is also very debatable if classic OOP is even good at all. JS was heading in the right direction, with more functional approaches, factory based object creation, etc... and now it's all back to square one.
Fortunately I work on a product where most devs have functional programming backgrounds, so the argument isn't too hard to make. But I fear for the community in general.
Why is this a big deal? Seems pretty trivial.
Example code: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Additionally, it's nice sugar for module loading, for modules that export multiple values.
I'm against block scope in a way. It's semi-useful, but it's just sugar. So many features of ES6 seem to be designed for people who don't want to bother learning javascript: classes, block scope, arrow syntax, etc. It's just trying to shoehorn javascript into the template every other language follows when javascript is not every other language.
That being said, I like iterators, weak maps, typed arrays, TCO, and so on.
No more `function() {}.bind(this);` since `() => {};` is equivalent. It makes it clearer for the reader that the code block inherits scope.
For example, scoping: For backwards compatibility reasons, var has to stay function scoped. But now we also have let, which has different scoping rules. Also, Symbols: [Symbol.iterator]() - what? Why?!
On the other hand, stuff like arrow functions, template strings, modules and tail optimization are awesome.
For example, the scoping rules: why have two separate scoping rules for let and var? To put it another way, would ES6 have let and var with differing scoping rules if it was designed today? If not, then it's a flaw (any difference from what a clean redesign would look like I consider a flaw).
Why not just make a simple and consistent syntax?
Not breaking millions of websites.
Maybe even orders of magnitude more than that.
So your fundamental choices are really an ES6 that is backwards-compatible or an ES6 that is not used.
An ES-latest runtime + transpilers would be all that browsers needed.
You don't have to agree with that decision obviously, but this isn't something that happened willy-nilly.
Welcome to C++.
And you can do it in (most) browsers today using a transpiler like Babel and a library like Blurebird, Q, co, or task.js.
The rest of the things are nice, but promises/generators (and eventually async/await) are game-changers.
Granted async/await syntax will make this a bit nicer:
async function funcA() {
await sleep(1000);
// ...Personally I'm most excited about the module system. It's by far the most confusing thing for our students (and hard to google since you end up going down the rabbit hole of build systems etc.)
e.g.
setTimeout(function(){alert(this.a)}.bind(this));
becomes simply
setTimeout(() => alert(this.a));
1. a unary :: operator for binding methods to their objects: `foo.then(::console.log)`
2. a binary :: operator for chaining: `x(a)::y(b)` (equivalent to `y.call(x(a), b)`)
ie. I can't write:
return new Promise((resolve, reject) => {
// Do some stuff, call resolve()/reject() on success/failure.
}).then((step1Val, resolve, reject) => {
// Do some stuff, call resolve()/reject() on success/failure.
// But this doesn't actually work, because the "then" callback
// doesn't get resolve/reject as args.
});
Instead I'm having to write this as the following, which is more verbose: return new Promise((resolve, reject) => {
// Do some stuff, call resolve()/reject() on success/failure.
}).then(new Promise((resolve, reject) = {
// Do some stuff, call resolve()/reject() on success/failure.
// But this way I don't get access to step1Val.
}));
Another bummer of this style is that that the second step doesn't get access to the first step's value.IMO you should use Babel and take advantage.
async function foo() {
try {
const valueOne = await promiseReturningFunction();
const valueTwo = await anotherAsyncFunction(valueOne);
return valueTwo;
} catch (err) {
alert(err);
}
}
foo(); //Returns a Promise.
----In this example, I've sequenced the resolution of two promises (async functions return Promises when invoked)
If either of these Promises reject or throw an error, or if any of the code within the try {} block throws an error, the error will be caught inline in the catch block (one area only).
You can also see that both Promise's return values are in-scope and continually usable (vs. losing scope with 'then' chains).
IMO threading an object through is probably the best method, unfortunately. You could use an Immutable Record or something to help keep it under control.
I would personally just use async/await to more explicitly handle the sequencing/binding.
Also how do you return a value from the second callback? Just return?
If throw and return work that way for then() functions, why not the same for the initial function?
Also some of indexedDB doesn't seem like it would fit with promises, since some operations have 3 or more callbacks (onsuccess, onerror, onupgradeneeded).
I can see the benefits of the indexedDb behavior: an open transaction is an exclusive resource, so leaving one dangling locks out other transactions. The auto-commit behavior means it's a lot harder to accidentally leave a transaction dangling. But it is unfortunate that this makes it not play nicely with promises.
somethingThatReturnsPromiseWithoutError()
.then((res) => {
return Promise.reject("some error");
})
.catch((err) => {
console.log(err); // => some error
});
Promise.resolve() and Promise.reject() return a promise resolved or rejected to the value passed in the first argument. Returning a promise in the fulfillment function passed to .then() chains the promises together. somethingThatReturnsPromise()
.then((foo) => {
return foo.bar;
// Or if you like it more verbose
return Promise.resolve(foo.bar);
// Or pass bar to a function modifying bar that returns a promise
return modifyBarReturnPromise(foo.bar);
})
.then((newBar) => {
console.log(newBar);
});
In your case, if you need step1Val in next promise chain, I personally do this, however people more familiar with promises may know of a better way to do it (maybe with something like Promise.all() or Promise.props() in the BlueBird library). somethingThatReturnsPromise()
.then((first) => {
doSomethingWithFirstAndReturnPromise(first)
.then((second) => {
console.log(first + second);
});
});then() returns a new promise resolved to the return value of the function. However, if that value is itself a promise, then it follows the promise chain and passes the eventual state to the next then()/catch() call.
The spec on mdn[0] doesn't mention asynchronous `then` or `catch` behavior. If the callback in Jquery `Deferred#then` returns a Deferred that deferred will be returned by `then`.
// Basic async function, resolves after n milliseconds
function wait(n) {
var promise = $.Deferred();
setTimeout(promise.resolve, n);
return promise;
}
wait(10)
.then(function() {
console.log('first'); // prints first after 10 milliseconds
return wait(10);
})
.then(function() {
console.log('second'); // prints second after 20 milliseconds
return 'done';
})
You can 'flip' a failed promise by returning a resolve promise in the fail callback. var promise = $.Deferred();
setTimeout(promise.reject, 100)
promise.then(null, function () {
return $.Deferred().resolve([]);
}).done(function(arg) {
console.log(arg); // Prints '[]'
})
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... function doSomething() {
return new Promise((resolve,reject)={
setTimeout(reject,100);
})
.catch(() => [])
}
doSomething().then((arg) => console.log(arg)); .then(val => doSomeStuff())
Thats it. The resulting promise will get resolved/rejected based on the return result of doSomeStuff()You can also use throw to reject a promise manually, or return Promise.reject()
.then(val => {
if (something) return successVal;
else throw new Error("Failure");
});
For more complex scenarios where I need the values from previous actions, I like the forgoing the chaining and using a join helper to unwrap any set of promises as needed: let url = getUrl(resource);
let data = url.then(url => fetch(url))
let updateData = d => _.assign(d, {field: 'newVal'}
let update = join(url, data, (url, data) => sendUpdate(url, updateData(data))Thanks for pointing out the key difference between arrow functions and inline functions being the context of 'this'. 'this' is going to be a source of confusion for a lot of people.
'this' without arrow functions is currently a huge source of confusion. I feel like the arrow functions will help to alleviate a lot of that confusion.
Async/await really is a nice sugar, check it out when you have a chance.
it's a little much
ES5 is the current standard in all major browsers, so using its functionality could be considered "safe". Thankfully we have transpilers (will convert your ES6 code to ES5), so you can use ES6 features in production today - just remember they are converted to an ES5 implementation of said functionality.
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
(i.e. this way if "new Archer('Legolas', 5, 6);" the x and y values will not be ignored.)I understand them and use them quite a bit. I just don't understand why they're in the language spec. They don't add anything new to the language (vs say generators or symbols).
Is it purely for performance?
This also means more feature-rich implementations can just extend the native promises instead of implementing everything from scratch, though for me this pretty much spells the end of third-party promise implementations.
In other words, they've been added for the same reason as the various new helper methods: not because they add new functionality, but because they provide a reliable well-defined and consistent solution to a very common problem.
Modules? That may be a while, and isn't a big concern since until HTTP2 is in wide effect (which may be really soon anyways) it makes more sense to just package the files in to one file (which you can do right now) using Browersify, JSPM, Webpack, or what have you than having the client request a whole bunch of javascript files at once.