你能解决 if 语句中的承诺吗?

can you resolve a promise in an if statement?

我对在 JS 中使用 promises 很陌生,我试图在另一个函数中执行更多代码之前先执行一个函数。唯一的问题是承诺的函数使用 if 语句来循环 setTimeout 命令。我添加了一个 if 语句以确保函数在我 resolve promise 之前完成循环,但 resolve 只是没有做任何事情。我使用 console.log 来确保 if 语句正在执行,并且在解析的任何一侧打印到控制台都没有问题。任何帮助将不胜感激。

代码:

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

在第一个 setTimeout 回调执行后,您将永远不会解析最外层调用的返回承诺。您最终将解决 innermost 调用返回的承诺,但这没有任何作用,因为从未使用过从那里返回的承诺。

可以 if (this.state < setting) resolve(this.changeTexture(loader, setting)) 但我推荐一种不同的,更不令人困惑(和非递归)的方式:

// 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
    }
}

这里我也制作了changeTexture函数async,所以我们可以在里面使用await,从而以更直接的方式实现延迟,并且可以构建一个常规的 while 循环整个事情。

(注意:从技术上讲,您现有的代码无条件地执行第一次迭代,因此 do ... while 会更准确,但我假设这只是您尝试使用 [= 构建它的方式的结果12=] 而不是你真正需要的。)

您可以在代码中的任意位置调用 resolvereject。但是您必须从您的 Promise 中恰好调用其中一个。

当您的 if 条件为假时,您的示例代码不会执行此操作,因此您需要修复该问题。

只要有一个闭包将 Promise 构造函数中的 resolve 变量与您在 if 语句中调用的 resolve() 链接起来,您就可以。但是在你的代码中你没有这个:

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.

这意味着当 if/else 最终调用 resolve() 时 resolve 与调用 changeTexture() 时返回的 Promise 无关。

做你想做的事情的方法是不要递归地调用 changeTexture,这样你就可以在 Promise 的 resolve 变量和你最终调用的 resolve 之间保持一个闭包。为此,只需将 setTimeout 回调与主 changeTexture 函数分开即可:

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();
        });
    }
}

或者,为了对代码进行最少的更改,您只需更改一行即可使代码正常工作:

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

arguments.callee 变量指向您传递给 setTimeout() => {... 函数。但是,arguments.callee 已被弃用并在严格模式下被禁用,因此请尽可能使用上面的 loop 函数。