In this post I propose to talk about ES6. ES6 gave us a lot of features that are definitely very cool. For me, these are a "map", "set", "weak map", "weak set", some functional programming elements (, Symbol.species, etc), Symbol, the spread operator and possibility to use it for breaking strings into tokens. But in this post, I want to talk about those main features of es6, which, in my opinion, have small downsides.

Let's start with the first imperfect innovation es6 - block scoping and let.

You probably know how "let" works, and in some cases, it is much more convenient than the good old var, right? Everything is great, but with the introduction of let in javascript, a new potentially dangerous concept of "temporary dead zone" has been introduced.

Let's see what a temporary dead zone is. We have a "typeof" operator in JS from the first versions. This operator guarantees us safe access to undeclared variables and allows us to check whether they were declared. But, with the advent of "let" we got a case when this trick does not work:
typeof oldFashionVar === undefined; // everything is going to work correctly here, we will predictably get "true".
var oldFashionVar = 1;

typeof brandNewLet === undefined; // we will get a ReferenceError here.
let brandNewLet = 1;
As we can see, using the "typeof" with the block scoping is not safe. This is what the "Temporary Dead Zone" term means.

Let's go ahead, and look into "const" declarations.

Constants are great unless they are declared globally. If we have a constant in the global scope, then we should consider that it will never be processed by the garbage collector, and will remain in memory forever. An unused constant can only be cleared from memory only in cases when the entire scope in which it is defined is cleared.

Global constants - will remain with you in memory forever. In addition, the same circumstance with the Temporary Dead Zone is also relevant for constants.

Now we will move on to one of the most commonly used features of es6 - the arrow function.

Arrow Functions are also very convenient. Binding to the context of the object in which it is defined is a very cool feature. But it also carries a potential danger if someone suddenly passes such a function into an argument and tries to redefine its context with ".bind", ".call" or ".apply".

It is expected that redefining the context of arrow function will fail, however it would be much safer if such usage of the arrow function would lead to an error. But this is not so, and in practice, this fact is a very common source of bugs. Here is a simple sample of this:
const robot = {
  powerSource: 'electricity'
const walk = () => {
  return `walking with the power of $ {this.powerSource}`;
};; // We will get a message "walking with the power of undefined"
It is impossible to override the lexical binding to context performed by arrow function, and you will not be notified that the attempt to override context binding failed. And you may encounter this in a situation when you will, for example, implement the Visitor design pattern. But again, the bad news here is not in that the calls to .bind, .call, .apply do not change the context, but that the override attempts fail quietly.

ES6 Promises

Here I will mention a feature of the ES6 API which has a less critical downside. With ES6 Promises, in principle, everything is not bad, they are convenient, although they have their well-known shortcomings.

However, there is one method in ES6 Promise API that is very dangerous to use. This is the "Promise.race()" method. It is dangerous if you pass a variable into it, and you can theoretically have an empty array value "[]" in this variable. In such case this Promise will hang forever.
const a = Promise.race([]); // this Promise is going to hang forever.

To conclude this post, I want to say that those facts that I was talking about here are just things which are worth keeping in mind. They can not be a reason to not use any of the mentioned ES6 features.