-8

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

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?

guest271314
  • 1
  • 15
  • 104
  • 177
  • @loganfsmyth Is the function parameter a mutatable or mutated `const` variable within the scope of the function? Is the fact that the `const` declaration is passed as a parameter allow the original value to be reassigned? – guest271314 Jul 28 '17 at 19:13
  • You've created a new variable named `state` as the argument to your `.then()` handler and that's what you're assigning to and that new variable is not `const`. It "shadows" or "overrides" the `const` variable of the same name declared in a higher scope. Change the name of your function parameter to from `state` to `localState` and you will see that assigning to `state` does not do what you thought it was doing. – jfriend00 Jul 28 '17 at 19:27
  • 3
    As I say at the end of my answer, as far as I am aware Javascript does not have a feature to prevent a lower scope from defining its own variable with the same name as a higher scoped `const` variable and thus "hiding" that higher scoped variable. That just isn't a feature of the language. I do not follow what your references to `Map` or `WeakMap` or `Class` have to do with this discussion or what `superconst` means. – jfriend00 Jul 28 '17 at 20:17
  • @jfriend00 A `WeakMap` key can only be the original key, yes? Or are there exception cases for the `WeakMap` implementation? The text at your Answer clarifies that an identifier at a function parameter is excluded from throwing an error. The concept is the possibility of an identifier which cannot reference any other value except the original and only assignment. Did not actually expect any outcome in particular with code at Question. The code is one attempt to try to find or create a pattern which satisfied the above requirement, or determine if the requirement is currently impossible. – guest271314 Jul 28 '17 at 20:21
  • @guest271314 - There's no special exception here for a function parameter. If you define `let state` or `var state` within the function, it's all the same. A function parameter is pretty much the same as declaring a local variable using `var`. Those override a higher scoped definition with the same name. – jfriend00 Jul 28 '17 at 20:25
  • @jfriend00 _"A function parameter is pretty much the same as declaring a local variable using `var`. Those override a higher scoped definition with the same name."_ Yes, trying to gather the limits of that implementation, and by what means that could be adjusted to prevent the higher scoped variable from being reassigned whatsoever. – guest271314 Jul 28 '17 at 20:29
  • A `weakMap` key will only find a match if it is the same value as was originally used when it was added to the `weakMap`. But, same value does not mean same variable. A variable itself is not a `weakMap` key. When you use a variable to assign a key in a `weakMap`, Javascript reads the current value from that variable and uses that as the key. The key itself has no connection at all to the variable from which it came. You can change that variable to your hearts content and the key in the `weakMap` is not affected at all. – jfriend00 Jul 28 '17 at 20:31
  • @jfriend00 Is `Symbol` unique? – guest271314 Jul 28 '17 at 20:32
  • I'm not sure what context you're asking about `Symbol` being unique, but yes I would call it unique. No two symbols are ever equal to each other so a newly created Symbol will always be a unique key in a `Map`. This feels like a bit of a random question that doesn't fit into current context. – jfriend00 Jul 28 '17 at 20:34
  • @jfriend00 If we declare a `const` or even `var` to be a `Symbol` in outer scope, which is used alone or as key for `Map` or `WeakMap`, even within a function where, for example, a copied identifier value references the local copy, `Map.get(/* Symbol reference within local scope of function */)` can only be retrieved with the original `Symbol` reference? Your Answer resolved inquiry as to OP, these comments are more directed to how to achieve otherwise. Not entirely sure how to properly phrase the inquiry, had best ask you now while you are now fielding these follow-up questions – guest271314 Jul 28 '17 at 20:36
  • 1
    Symbols can be copied to multiple variables just fine. `let a = Symbol(); let b = a;` Then, you can use either `a` or `b` to access the same element in a `Map`. I'm glad to see you have accepted an answer because this is turning into a long, winding discussion rather than the type of Q&A that is on-topic for stack overflow. You might want to ask a new question which explains what problem you're really trying to solve though I suspect it is not something that Javascript supports. – jfriend00 Jul 28 '17 at 20:40

2 Answers2

4

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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

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.

Vinayak Sakhare
  • 768
  • 1
  • 7
  • 14
  • Is the function parameter a mutatable or mutated`const` variable within the scope of the function? – guest271314 Jul 28 '17 at 19:13
  • Here is what is happening within function block : var state = state //(this state is your const variable) const variable is not mutating. You are just using its value and defining new variable with same name within function block. – Vinayak Sakhare Jul 28 '17 at 19:17
  • Why does the pattern using `Promise` constructor throw and error? – guest271314 Jul 28 '17 at 19:22
  • @guest271314 - A function parameter works just like a local variable (scoped to the function). It is not `const`. It behaves the same, no matter what you passed in it so it does not matter that you passed a `const` value. Only the original variable is `const`. This is a new variable that is not `const`. – jfriend00 Jul 28 '17 at 19:34
  • Promise constructor gives error because, you are not creating new variable for the function. you can try this.state inside constructor to make it work. – Vinayak Sakhare Jul 28 '17 at 19:36
  • @jfriend00 _"This is a new variable that is not `const`"_ The original `const` variable value is copied internally? – guest271314 Jul 28 '17 at 19:37
  • @VinayakSakhare _"you can try this.state inside constructor to make it work"_ What would be the expected result of using `this` within `Promise` constructor resolver function? – guest271314 Jul 28 '17 at 19:38
  • @guest271314 - Yes, it's copied. As in `const a = 1; let b = a;` `b` is not const. – jfriend00 Jul 28 '17 at 19:39
  • @jfriend00 _"Yes, it's copied."_ Interesting. We are using the same variable identifier, yes? Why is there a difference between `Promise.resolve()` and `Promise` constructor patterns? Then it is possible to reassign a `const` variable under certain conditions? – guest271314 Jul 28 '17 at 19:42
  • @guest271314 - `this` is not the right solution here. Just change the name of your function parameter to not conflict and then you can directly access the higher scoped variable and it is still `const`. Javascript allows you to create conflicting variable definitions like this and the closer scoped variable temporarily "hides" the higher scoped variable, sometimes causing confusion. – jfriend00 Jul 28 '17 at 19:44
  • @guest271314 - You are not reassigning a `const` variable in any way - that is not possible. You have defined a new variable with the same name which has overriden the higher scoped variable within that scope - the higher scoped variable is still there and remains unchanged when you access it from outside the overriden scope. – jfriend00 Jul 28 '17 at 19:46
  • @guest271314 : "What would be the expected result of using this within Promise constructor resolver function?" this will create a separate variable for that function. like your first code. Under any circumstances you can not override const variable again. – Vinayak Sakhare Jul 28 '17 at 19:46
  • @jfriend00 Was not trying to update the value. The inquiry is based on the boundaries of `const` declarations. That is, in which contexts can the original identifier be "reassigned", or reference a different object altogether. In which contexts will an error be thrown; the limits of prospectively relying on an identifier to reference the same original object? Under which block scope conditions a JavaScript engine will throw an error if reassignment of the original `const` identifier is attempted? If we had the incorrect presumption that any attempt to redefine a `const` would throw an error – guest271314 Jul 28 '17 at 19:48
  • @guest271314 - It will throw an error anytime you assign to a `const` variable. You aren't assigning to a `const` variable here. You are assigning to a locally declared variable (in your function parameter) and that is not `const`. Because you gave the function parameter the same name as the higher scoped variable, it overrides that variable within this scope. Stop creating naming conflicts like this and your confusion will disappear. Change `.then(state => {` to `.then(localState => {` and then when you try to assign to `state`, you will get an exception. `localState` is a non `const` copy. – jfriend00 Jul 28 '17 at 19:53
  • @jfriend00 Was attempting 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? – guest271314 Jul 28 '17 at 20:04
  • 1
    @guest271314 - I moved my commentary into an answer. – jfriend00 Jul 28 '17 at 20:11
  • @jfriend00 Updated Question to include text of last comment, if possible, to avoid confusion as to what specific identifier functionality and limitation am attempting to inquire about within the language – guest271314 Jul 28 '17 at 20:15