Look at the code below. Look at test
getter. Why does not const name = this.person.name
throw an error while const processPerson = () => this.person.name
does throw an error?
interface Person { name: string; age: number }
class TestClass {
get person(): Person | undefined {
if (Math.random() > 0.5) return undefined
return { name: 'Bob', age: 35 }
}
get test() {
if (!this.person) return undefined
const name = this.person.name // No error
const processPerson = () => this.person.name // Object is possibly 'undefined'.(2532)
return processPerson()
}
}
CodePudding user response:
Problem
you need to understand that in a function there will be a new scope imagine if you keep the same code and return processPerson
instead of processPerson()
and run this code
const test = new TestClass()
test.person = { name: '', age: 0 }
const fn = test.test()
fn() // is ''
test.person = undefined
fn() // error because this.person is undefined
yes that means the this.person
in processPerson
can perfectly is undefined
since it doesn't need to pass the this.person
check
Resolve
you have 2 workarounds:
if you want
processPerson
function to return realtime value ofthis.person.name
you must acceptthis.person
check in functionotherwise you can do this
class TestClass {
get test() {
const { person } = this
if (!person) return undefined
const name = person.name
const processPerson = () => name
return processPerson()
}
}
CodePudding user response:
The problem is that your test calls the getter twice, and since the getter randomly returns either undefined or a value, the two lines don't get the same value.
Looks like the first call to the getter gets a value, while the second call gets undefined.
To fix this, call the getter once at the beginning of the test method, and assign the result to a local variable. Then test the local variable instead of calling the getter, like this
test() {
const subject = this.person;
if (!subject) return undefined
const name = subject.name
const processPerson = () => subject
return processPerson()
}
CodePudding user response:
This behaviour can happen when you don't bind this
, because when defining a new function, it will have it's own this
, which will override/ clash with the class.
In order for this to work, you shouldn't use arrow functions, since they don't have an internal this
pointer.
If you want to use this
inside processPerson, run:
return processPerson.bind(this)
Alternatively you could store the value of this
in another variable name, however I see it as a less adequate solution to inherit some properties, but it can be deep copied with Object.assign or something equivalent.
The final snippet will be something like this:
interface Person { name: string; age: number }
class TestClass {
get person(): Person | undefined {
if (Math.random() > 0.5) return undefined
return { name: 'Bob', age: 35 }
}
get test() {
if (!this.person) return undefined
const name = this.person.name // No error
const processPerson = (function() {
return this.person.name;
}).bind(this);
return processPerson()
}
}
This guard check: if (!this.person) return undefined
will prevent that this.person is undefined when we create the returned function, but won't prevent it to be undefined when the function is called and runs, and since the returned function's this
is not freezed when it's generated, it'll be undefined behaviour (non deterministic, meaning you can't trust the value). The same behaviour could be introduced in a function that uses global variables, so in order to keep this
encapsulated, it's good to bind it.
CodePudding user response:
Let me try to explain you what is typescript thinks about your code and how it acts:
class TestClass {
// this is the worst possible getter for a typescript compiler
// the random nature of the getter deceives both the compiler and the developer
get person(): Person | undefined {
if (Math.random() > 0.5) return undefined
return { name: 'Bob', age: 35 }
}
get test() {
if (!this.person) return undefined
// In here TS compiler thinks that accessing this.person.name
// is safe because the above line has a guard for it.
// But it will throw runtime errors by P chance
// Note that TS doesn't aware of its randomness here
const name = this.person.name //
// If we describe this line we call it a local arrow function definition
// Since it's a definition and also there is no nullability guard in it TS is right about complaining here
// Since it's a definition it is not deterministic where it
// will be called in the code, and the compiler can't be sure whether it is null or not.
const processPerson = () => this.person.name // Object is possibly 'undefined'.(2532)
return processPerson()
}
}
The other answers have already good practices to avoid the error and possible runtime issues.