I'm quite new to using promises in JS, and im trying to have a function execute before executing some more code in another function. Only issue is that the promised function uses an if statement to loop a setTimeout command. I added an if statement to make sure the function is done looping before i resolve the promise but the resolve just isn't doing anything. I used console.log to make sure that the if statement is executing and it has no problems printing to the console on either side of the resolve. Any help would be greatly appreciated.
Code:
async makeToast(loader, toaster){
toaster.texture = loader.resources['toaster_down'].texture;
this.interactive = false;
this.x = toaster.x;
this.y = toaster.y - 100;
let transform = {y: this.y};
let popDown = new TWEEN.Tween(transform)
.to({y: toaster.y - 50}, 200)
.onUpdate(() => this.y = transform.y);
popDown.start();
await this.changeTexture(loader, toaster.setting)
console.log('toasting done');
this.interactive = true;
}
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
setTimeout(() => {
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting) this.changeTexture(loader, setting);
else if(this.state == setting) resolve();
}, 1000);
});
}
After the first setTimeout callback executes, you will never resolve the outermost call's returned promise. You will resolve the innermost call's returned promise eventually, but that does nothing since the promise returned from there is never used.
You could write if (this.state < setting) resolve(this.changeTexture(loader, setting)) but I'd recommend a different, far less confusing (and non-recursive) way instead:
// This could be defined globally, can be useful elsewhere too
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
// This is in your object
async changeTexture (loader, setting) {
while (this.state < setting) {
await delay(1000)
this.state++
this.texture = loader.resources[`bread${this.state}`].texture
}
}
Here I've made the changeTexture function async as well, so we can use await inside and therefore implement the delay in a more straight-forward manner and can build a regular while loop around the whole thing.
(Note: Technically your existing code does the first iteration unconditionally, so a do ... while would be more accurate, but I'm assuming that is just a result of the way you tried building it with setTimeout and not really what you need.)
You can as long as there is a closure linking the resolve variable in the Promise constructor with the resolve() you call in your if statement. However in your code you don't have this:
class SomeClass {
// ...
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
setTimeout(() => {
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting)
this.changeTexture(loader, setting); <----------.
else if(this.state == setting) |
resolve(); <-- There is a closure to this /
}, 1000); /
}); .---------------------------'
} |
} However this function call will have it's own
"resolve" variable that is no longer captured
by this closure.
This means that when the if/else finally calls resolve() that resolve has nothing to do with the Promise you returned when you call changeTexture().
The way to do what you want is to not call changeTexture recursively so that you maintain a closure between the Promise's resolve variable and the resolve you finally call. To do this simply separate your setTimeout callback from the main changeTexture function:
class SomeClass {
// ...
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
let loop = () => { // use arrow function to capture "this"
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting) setTimeout(loop, 1000);
else if(this.state == setting) resolve();
}
loop();
});
}
}
Alternatively for minimal change in code you can get your code working by changing only one line:
class SomeClass {
// ...
changeTexture(loader, setting){
return new Promise((resolve, reject) => {
setTimeout(() => {
this.state++;
this.texture = loader.resources[`bread${this.state}`].texture;
if(this.state < setting) setTimeout(arguments.callee(),1000); // <----THIS
else if(this.state == setting) resolve();
}, 1000);
});
}
}
The arguments.callee variable points to the () => {... function you pass to setTimeout. However, arguments.callee is deprecated and is disabled in strict mode so use the loop function above if possible.
You can call resolve and reject from anywhere in your code you wish. But you must call exactly one of them exactly once from within your Promise.
Your sample code doesn't do that when your if-condition is false, so you need to fix that.