Hijacking this question (since it's the first to pop up upon searching "setTimeout this undefined") to post a similar problem I had which suits the title, but not the question's content.
Question
I have this class Foo, and I need to await for a condition which involves members of Foo.
class Foo {
constructor(bar) { this.bar = bar; }
async async_increment_bar_1(new_bar) {
const poll = resolve => {
if (this.bar === new_bar) {
resolve();
} else {
this.bar += 1;
setTimeout(_ => poll(resolve), 1000, new_bar);
}
};
await new Promise(poll);
}
}
The first way works just fine, however I find the second one to be more concise, and it also does not pollute the namespace with the poll()
function.
class Foo {
constructor(bar) { this.bar = bar; }
async async_increment_bar_2(new_bar) {
await new Promise(function poll(resolve) {
if (this.bar === new_bar) { // <-- this will raise error
resolve();
} else {
this.bar += 1;
setTimeout(_ => poll(resolve), 1000, new_bar);
}
});
}
}
However, this raises the following error:
TypeError: Cannot read properties of undefined (reading 'bar')
How can I fix this?
Answer
As per the top answer:
When functions are passed by reference, they lose their reference to this. You're losing this reference when calling setTimeout.
Functions have a bind() method that basically return a new function with a corrected reference to this.
To fix the second code snippet, it's necessary to bind the this
reference to the poll()
function, at every instance where the poll()
function is passed by reference; both at definition and when it's called again, inside the setTimeout
lambda.
class Foo {
constructor(bar) { this.bar = bar; }
async async_increment_bar_2(new_bar) {
await new Promise(function poll(resolve) {
if (this.bar == new_bar) {
resolve();
} else {
this.bar += 1;
setTimeout(_ => poll.bind(this)(resolve), 1000, new_bar);
}
}.bind(this));
}
}
Full code:
class Foo {
constructor(bar) { this.bar = bar; }
async async_increment_bar_1(new_bar) {
const poll = resolve => {
if (this.bar === new_bar) {
resolve();
} else {
this.bar += 1;
setTimeout(_ => poll(resolve), 1000, new_bar);
}
};
await new Promise(poll);
}
async async_increment_bar_2(new_bar) {
await new Promise(function poll(resolve) {
if (this.bar == new_bar) {
resolve();
} else {
this.bar += 1;
setTimeout(_ => poll.bind(this)(resolve), 1000, new_bar);
}
}.bind(this));
}
}
main = async () => {
let foo = new Foo(0);
console.log(`Initially foo.bar is ${foo.bar}`);
await foo.async_increment_bar_2(3);
console.log(`Finally foo.bar is ${foo.bar}`);
}
main();