代码之家  ›  专栏  ›  技术社区  ›  NickJ

承诺链-等待长时间运行

  •  -1
  • NickJ  · 技术社区  · 3 年前

    我有一个typescript程序,它调用外部服务中的各种命令。它使用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的目的是每分钟检查一次状态,并在长时间运行的任务报告完成时履行承诺。但实际发生的情况是,每次检查之间不会等待一分钟,而且很多行都是“Status is IN_PROGRESS,waiting…”出现,并且进程变得没有响应。

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

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

    0 回复  |  直到 3 年前
        1
  •  0
  •   T.J. Crowder    3 年前

    我在评论中说:

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

    您回答:

    “上面的代码不是这样做的”——我认为是这样——这些方法中的每一个都返回一个承诺,当任务完成时,承诺就会实现。

    这可能是你问题的基础。 then catch 期望函数,而不是承诺。我所指的代码是:

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

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

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

    此时,由启动的任务 doTask , doTask2 doTask3 都是并行运行的;他们会兑现承诺,但任务之间没有强制要求。你可以在这里看到:

    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 doTask2 doTask3 用调用
    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");
    .作为控制台包装器{
    最大高度:100%!重要的
    }

    或者,在 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}`);
        }
    })();
    .作为控制台包装器{
    最大高度:100%!重要的
    }

    笔记 有些非标准库在调用之前不会启动所承诺的异步工作 然后 兑现承诺。这些都是异常值。在正常情况下,promise是工作的完成标记 已经在进行中 (就是这样 异步 函数行为)。

        2
  •  0
  •   Bergi    3 年前

    使用 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 + setTimeout What is the JavaScript version of sleep()? :

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

    最后,您的promise链需要将回调函数传递给 .then() (正如@TJCrowder所评论的那样——其他解决方案见他的回答):

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