Why does .then() chained to Promise.resolve() allow const declaration to be reassigned?

You are not assigning to the const variable. You are instead assigning to the function parameter that you gave the same name. That function parameter is a non-const copy of the variable so you are allowed to assign to it.

I will try to collect all my comments into a more fully explained answer.

In code here:

"use strict"
const state = "123";
Promise.resolve(state).then(state => {
  console.log(state); // `"123"`
  state = 456; // reassign `const` variable `state` to `"456"`
  return state
}).then(state => console.log(state)) // `"456"`
  // not reached
.catch(err => console.error(err.message));

First, you define your const state = "123" variable. Any attempt to change the contents of that exact state variable will throw an exception.

Then, when you do this:

Promise.resolve(state).then(state => {

That declares a .then() handler function that takes one argument and the name of that argument is state. When the .then() handler is called, whatever is passed as that one argument to the .then() handler is copied into this new argument variable named state. Function arguments are not const. They can be assigned to.

Because you’ve now created TWO separate variables with the same name and one is at a higher scope, when you are inside the .then() handler, that function argument named state “overrides” or “hides” the other variable of the same name. When you attempt to access state inside the .then() handler, the ONLY variable you can access when using that name is the function parameter. That function parameter is a copy of the other state variable by virtue of being passed to the .then() handler as an argument. All function arguments are copies. Javascript has no true reference variable types.

Furthermore function arguments are not const so you can assign to them.

So, when you state = "456"; inside that .then() handler, you are just assigning to the function argument. Because you’ve created a naming conflict, there is actually no way to access the higher scoped const state variable. The JS interpreter finds the definition that is closest in scope to where you are attempting to access it.


I think your confusion will be cleared up if you just stop creating a conflicting variable name. If you do it like this (name the parameter localState):

"use strict"
const state = "123";
Promise.resolve(state).then(localState => {
  console.log(state); // `"123"`
  state = 456; // reassign `const` variable `state` to `"456"`
  return state
}).then(state => console.log(state)) // `"456"`
  // not reached
.catch(err => console.error(err.message));

Then, you will see an exception when you attempt to assign to state because you have not created a conflicting local variable with that same name so your attempt to assign state = 456 will indeed be attempting to assign to a const variable and the interpreter will object.


As best I know, Javascript has no way to prevent overriding a higher scoped variable with a newly declared variable of the same name in the local scope. That just isn’t a language feature. When the interpreter resolves a variable name, it searches the scope hierarchy from local to global so local definitions are found (and used) first. Higher scoped definitions are “overriden” or “hidden” within that scope. That’s just how they designed variable name resolution to work in the language.

There are many benefits to this too in that somebody suddenly declaring a higher scoped variable that you aren’t using or aren’t even aware of will never accidentally break your lower scoped declarations. When you yourself declaring a conflict and you actually want to use the higher scoped named, that’s just a coding mistake. You have to not declare a conflicting name if you intend to use the higher scoped variable of the same name.

Leave a Comment