Why does .then() chained to Promise.resolve() allo

2019-01-12 13:20发布

问题:

Given that a variable declared with const cannot be reassigned or deleted see

  • Are there constants in JavaScript?

  • JavaScript const Keyword

  • Is it possible to delete a variable declared using const??

why is it possible to reassign a value to a variable declared with const within a function passed to .then() chained to Promise.resolve() where const variable is passed, but it is not possible to reassign the const variable with function passed to .then() chained to Promise constructor where const variable is passed to resolve() parameter of Promise constructor resolver function?

"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));

{
  "use strict";
  const state = "123";
  new Promise(resolve => {
    console.log(state); // `"123"`
    state = "456"; // reassign `const` variable `state` to `456`
    resolve(state);
  })
  .then(state => {
    console.log(state);
  })
  // `Error: Assignment to constant variable.`
  .catch(err => console.error(err.message)); 
}


Edit, Updated

To clarify the basis and motivation for the inquiry, the Question is an attempt to determine when an identifier can be used which is the same as a const declaration, and when that procedure is not possible. In essence, trying to create an identifier which throws an error in any scope where an attempt is made to assign the identifier a different value - whether function scope, block scope or anywhere in the code - a "superconst" indetifier, or the closest to that description currently possible, depending on the engine. Are Map or WeakMap or class the closest we currently have at newest browser implementations?

回答1:

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.



回答2:

Here constant state variable is not changing. You are changing the parameter value which is passed to the resolve function. You can verify this by simply doing console.log(state) after all your code and output will be 123.