【问题标题】:Promise chain - waiting for long-running operation承诺链 - 等待长时间运行
【发布时间】:2021-10-22 17:44:09
【问题描述】:

我有一个打字稿程序,它在外部服务中调用各种命令。它使用 Promises 来处理成功完成或错误条件。有一个命令序列,例如:

server.doTask1()
  .then(server.doTask2())
  .then(server.doTask3())
  .catch( ... handle the error )

在继续下一个任务之前成功完成每个任务很重要,因此如果有任何失败,它会直接进入 catch() 而不尝试更多任务。

问题的出现是因为其中一个任务本身是异步返回的——一旦任务成功启动,承诺就会实现。我可以调用另一个外部命令来检查进度。我的尝试如下所示:

server.doTask1()
  .then(server.doTask2())
  .then(server.doTask3())
  .then(server.doLongRunningTask())

还有……

doLongRunningTask() {
    return new Promise((resolve, reject) => {
        this.runCommand('external command')
        .then((result) => resolve(this.waitForCommand(result.Id)))
        .catch((err) => reject(err));
    });
}

private waitForCommand(commandId : string) : Promise<any> {

    while (true) {

        this.runCommand(`external command status report ${commandId}`)
            .then((result) => {
                if (result.Status==='IN_PROGRESS') {
                    console.log(`Status is ${result.Status}, waiting...`);
                    this.wait(60);
                } else {
                    console.log(`Status complete`);
                    return Promise.resolve();
                }
            })
            .catch((error) => {
                return Promise.reject(error);
            })

    } 
}

public wait(seconds : number) {
    var now = new Date().getTime();
    while(new Date().getTime() < now + (seconds*1000)){ /* Do nothing */ }
}

目的是让waitForCommand每分钟检查一次状态,并在长时间运行的任务报告完成时履行承诺。但实际发生的情况是,它在每次检查之间都没有等待一分钟,并且出现了许多行“状态为 IN_PROGRESS,正在等待...”,并且进程变得无响应。

为了等待,我尝试了await new Promise(res =&gt; setTimeout(res, 60000));,但 Typescript 编译器抱怨“等待表达式只允许在异步函数中和模块的顶层使用”

我怎样才能达到我想要的结果?

【问题讨论】:

  • “在继续下一个任务之前成功完成每个任务很重要” 这不是上面的代码所做的,除非doTask2doTask3 不执行它们的任务,而是返回稍后调用时将执行它们的函数。
  • “问题出现是因为其中一个任务本身是异步返回的——一旦任务成功启动,承诺就会实现。” 这似乎是一个非常奇怪的设计。使用 Promise 的目的是在异步任务完成之前设置它们。
  • 恐怕不清楚你真正在做什么,这使得基本上不可能给你任何具体的帮助。 :-|
  • 这个问题有帮助吗?stackoverflow.com/questions/68594022/…
  • @T.J.Crowder “这不是上面的代码所做的” - 我认为确实如此 - 这些方法中的每一个都返回一个承诺,该承诺在任务完成时实现。

标签: typescript promise


【解决方案1】:

我在评论中说:

“重要的是在继续下一个任务之前成功完成每个任务” 这不是上面的代码所做的,除非 doTask2 和 doTask3 不执行它们的任务,而是返回函数这将在稍后调用时执行。

你回复了:

“这不是它上面的代码所做的”——我认为它确实如此——这些方法中的每一个都返回一个承诺,该承诺在任务完成时实现。

这可能是您问题的根源。 thencatch 期待功能,而不是承诺。我指的代码是:

server.doTask1()
  .then(server.doTask2())
  .then(server.doTask3())
  .catch( ... handle the error )

当该代码运行时,同步发生以下情况:

  1. server.doTask1 被调用,开始工作并返回一个承诺(我们称之为“p1”)
  2. server.doTask2 被调用,开始它的工作并返回一个 promise ("p2")
  3. then 在“p1”上调用,传入“p2”;它返回一个承诺(“p3”)
  4. server.doTask3 被调用,开始它的工作并返回一个 promise ("p4")
  5. then 在“p3”上调用,传入“p4”;它返回一个承诺(“p5”)
  6. catch 在“p5”上被调用(返回一个从未使用过的承诺)

此时doTaskdoTask2doTask3启动的任务都在并行运行;他们会兑现承诺,但任务之间没有强加顺序。你可以在这里看到:

const start = Date.now();
const elapsed = () => String(Date.now() - start).padStart(4);
const log = msg => console.log(`[${elapsed()}] ${msg}`);

const server = {
    doTask1() {
        return new Promise(resolve => {
            log("task1 started");
            setTimeout(() => {
                log("task1 complete");
                resolve("result1");
            }, 200);
        });
    },
    doTask2() {
        return new Promise(resolve => {
            log("task2 started");
            setTimeout(() => {
                log("task2 complete");
                resolve("result2");
            }, 300);
        });
    },
    doTask3() {
        return new Promise(resolve => {
            log("task3 started");
            setTimeout(() => {
                log("task3 complete");
                resolve("result3");
            }, 100);
        });
    },
};

log("Running the code");
server.doTask1()
    .then(server.doTask2())
    .then(server.doTask3())
    .catch(() => log(`ERROR: ${error}`))
    .then(() => log("Chain complete"));
log("Done running the code");
.as-console-wrapper {
    max-height: 100% !important;
}

注意任务 1-3 如何立即开始

要让它们一个接一个地运行,你需要传递一个函数,而不是一个promise,到 then:

server.doTask1()
  .then(() => server.doTask2())
  //    ^^^^^^
  .then(() => server.doTask3())
  //    ^^^^^^
  .catch( ... handle the error )

我在那里做了两个假设:

  1. 重要的是 this doTask2doTask3 被调用
  2. 您不希望将上一个任务的结果传递给下一个任务。

如果其中一个或两个都不正确,您可以通过其他方式编写它。

现场示例:

const start = Date.now();
const elapsed = () => String(Date.now() - start).padStart(4);
const log = msg => console.log(`[${elapsed()}] ${msg}`);

const server = {
    doTask1() {
        return new Promise(resolve => {
            log("task1 started");
            setTimeout(() => {
                log("task1 complete");
                resolve("result1");
            }, 200);
        });
    },
    doTask2() {
        return new Promise(resolve => {
            log("task2 started");
            setTimeout(() => {
                log("task2 complete");
                resolve("result2");
            }, 300);
        });
    },
    doTask3() {
        return new Promise(resolve => {
            log("task3 started");
            setTimeout(() => {
                log("task3 complete");
                resolve("result3");
            }, 100);
        });
    },
};

log("Running the code");
server.doTask1()
    .then(() => server.doTask2())
    .then(() => server.doTask3())
    .catch(() => log(`ERROR: ${error}`))
    .then(() => log("Chain complete"));
log("Done running the code");
.as-console-wrapper {
    max-height: 100% !important;
}

或者,在async 函数中:

try {
    await server.doTask1();
    await server.doTask2();
    await server.doTask3();
    // ...
} catch (error) {
    // ...handle/report error...
}

现场示例:

const start = Date.now();
const elapsed = () => String(Date.now() - start).padStart(4);
const log = msg => console.log(`[${elapsed()}] ${msg}`);

const server = {
    doTask1() {
        return new Promise(resolve => {
            log("task1 started");
            setTimeout(() => {
                log("task1 complete");
                resolve("result1");
            }, 200);
        });
    },
    doTask2() {
        return new Promise(resolve => {
            log("task2 started");
            setTimeout(() => {
                log("task2 complete");
                resolve("result2");
            }, 300);
        });
    },
    doTask3() {
        return new Promise(resolve => {
            log("task3 started");
            setTimeout(() => {
                log("task3 complete");
                resolve("result3");
            }, 100);
        });
    },
};

(async () => {
    try {
        await server.doTask1();
        await server.doTask2();
        await server.doTask3();
        // ...
        log("Chain complete");
    } catch (error) {
        // ...handle/report error...
        log(`ERROR: ${error}`);
    }
})();
.as-console-wrapper {
    max-height: 100% !important;
}

注意:有些非标准库在你调用then 之前不会启动承诺的异步工作。这些是异常值。在正常情况下,promise 是工作的完成标记已经在进行中(这就是 async 函数的行为方式)。

【讨论】:

    【解决方案2】:

    正确使用async/await,一切顺利:

    async doLongRunningTask() {
        const result = await this.runCommand('external command')
        return this.waitForCommand(result.Id);
    }
    
    private async waitForCommand(commandId : string) : Promise<any> {
        while (true) {
            const result = this.runCommand(`external command status report ${commandId}`);
            if (result.Status==='IN_PROGRESS') {
                console.log(`Status is ${result.Status}, waiting...`);
                await this.wait(60);
            } else {
                console.log(`Status complete`);
                return;
            }
        } 
    }
    

    对于wait 方法,您实际上需要异步执行,而不是忙于等待。见Combination of async function + await + setTimeoutWhat is the JavaScript version of sleep()?

    public wait(seconds : number): Promise<void> {
        return new Promise(resolve => setTimeout(resolve, seconds*1000));
    }
    

    最后,您的承诺链需要将回调函数传递给.then()(@TJCrowder 也说过 - 请参阅他的答案以获得替代解决方案):

    server.doTask1()
      .then(() => server.doTask2())
      .then(() => server.doTask3())
      .catch(err => { /* ... handle the error */ });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-19
      • 2013-09-16
      • 1970-01-01
      • 2021-11-09
      • 2018-10-17
      • 2019-04-29
      • 1970-01-01
      相关资源
      最近更新 更多