【问题标题】:Chaining Promises in Typescript在 Typescript 中链接 Promise
【发布时间】:2016-08-05 18:50:24
【问题描述】:

如何将 async/await 代码 (Typescript + es6 target) 转换为使用链式 Promise.then()

例如:

function mockDelay<T>(getValue:()=>T): Promise<T> {
    return new Promise<T>(resolve=>setTimeout(()=>resolve(getValue()), 10));
}
// Assume blackbox implementation
class Service {
    constructor(private i=1, private callCount=0){}
    opA() : Promise<number> { 
        this.callCount++; 
        return mockDelay(()=>this.i+=1);
    }
    opB(value:number) : Promise<number> {
        this.callCount++;    
        return mockDelay(()=>this.i+=value);
    }

    opC(): Promise<number> {
        return mockDelay(()=>this.i+=2);
    }

    isA(): Promise<boolean> { return mockDelay(()=>this.callCount%2===0); }
    isC(): Promise<boolean> { return mockDelay(() =>true); }
}

// Convert this async/await code to using chained Promises
async function asyncVersion(): Promise<string[]>{
    let expected:string[] = [];
    let lib = new Service();
    let sum = 20;
    let lastValue = 0;
    while (sum > 0) {
        expected.push(`${sum} left`);
        if (await lib.isA())
        {
            expected.push("Do A()");
            lastValue = await lib.opA();
            sum -= lastValue;
        }
        else
        {
            expected.push("Do B()");
            lastValue = await lib.opB(lastValue);
            sum -= lastValue*3;
            if (await lib.isC()) {
                expected.push("Do C()");
                sum += await lib.opC();
            }
        }
    }
    expected.push("All completed!");
    return expected;
};

function chainPromiseVersion(): Promise<string[]>{
    // How to convert the asyncVersion() to using chained promises?
    return Promise.resolve([]);
} 

// Compare results
// Currently running asyncVersion() twice to ensure call results are consistent/no side effects
// Replace asyncVersion() with chainPromiseVersion() 
Promise.all([asyncVersion(), asyncVersion() /*chainPromiseVersion()*/])
    .then(result =>{
        let expected = result[0];
        let actual = result[1];
        if (expected.length !== actual.length) 
            throw new Error(`Length: expected ${expected.length} but was ${actual.length}`);
        for(let i=0; i<expected.length; i++) {
            if (expected[i] !== actual[i]){
                throw new Error(`Expected ${expected[i]} but was ${actual[i]}`);
            }
        }
    })
    .then(()=>console.log("Test completed"))
    .catch(e => console.log("Error: "  + e));

我知道我可以使用 Babel (Github example) 将 es6 代码转换为 es5。

这个问题是关于手动重写 async/await 代码以使用链式 Promise。

我可以像下面这样转换简单的例子。

// Async/Await
(async function(){
    for (let i=0; i<5; i++){
        let result = await mockDelay(()=>"Done " + i);
        console.log(result);
    }
    console.log("All done");
})();

// Chained Promises
(function(){
    let chain = Promise.resolve(null);
    for (let i=0; i<5; i++){
        chain = chain
            .then(()=>mockDelay(()=>"Done " + i))
            .then(result => console.log(result));
    }
    chain.then(()=>console.log("All done"));
})();

但不知道如何转换上面的示例,其中:

  • 受承诺结果影响的循环条件
  • 必须一个接一个地执行(不是Promise.all()

【问题讨论】:

  • 你为什么要这样做?
  • 学习如何。如果我的最终目标只是让 async/await 代码针对 es5,那么我已经在使用 Babel 进行编译了。

标签: typescript promise async-await


【解决方案1】:

感谢 Bergi 的回答,我想我已经知道如何逐步从 async/await 转换为链式 promise

我创建了一个辅助函数 promiseWhile 来帮助自己:

// Potential issue: recursion could lead to stackoverflow
function promiseWhile(condition:()=>boolean, loopBody: ()=>Promise<any>): Promise<any> {
    if (condition()) {
        return loopBody().then(()=>promiseWhile(condition, loopBody));
    } else {
        // Loop terminated
        return null;
    }
}

我使用的步骤:

  • 每当await op() 被命中时,转换为return op().then(()=&gt;{...})
    • 其中{...}await 之后的代码(包括来自await 的赋值)
  • 这会导致一些深度嵌套,但如果我按照这些步骤操作,我发现自己犯错误的可能性会降低
  • 完成并验证后,我就可以进去清理了

// Converted
function chainPromiseVersion(): Promise<string[]>{
    let expected:string[] = [];
    let lib = new Service();
    let sum = 20;
    let lastValue = 0;
    return promiseWhile(
        // Loop condition
        ()=>sum>0,

        // Loop body 
        ()=> {
            expected.push(`${sum} left`);

            return Promise.resolve(null)
            .then(()=>lib.isA())
            .then(isA => {
                if (isA) {
                    expected.push("Do A()");
                    return lib.opA()
                        .then(v =>{
                            lastValue = v;
                            sum -= lastValue;
                        });
                }
                else {
                    expected.push("Do B()");
                    return lib.opB(lastValue)
                            .then(v=>{
                                lastValue = v;
                                sum -= lastValue*3;
                                return lib.isC().then(isC => {
                                    if (isC) {
                                        expected.push("Do C()");
                                        return lib.opC().then(v => {
                                            sum += v;
                                        });
                                    }
                                });
                            });
                }
            }) // if (lib.isA()) 
        }) // End loop body
        .then(()=>expected.push("All completed!"))
        .then(()=>expected);
}

【讨论】:

    【解决方案2】:

    awaits 变成 then 调用——通常是嵌套调用,以使作用域和控制流有效——循环变成递归。在你的情况下:

    (function loop(lib, sum, lastValue){
        if (sum > 0) {
            console.log(`${sum} left`);
            return lib.isA().then(res => {
                if (res) {
                    console.log("Do A()");
                    return lib.opA().then(lastValue => {
                        sum -= lastValue;
                        return loop(lib, sum, lastValue);
                    });
                } else {
                    console.log("Do B()");
                    return lib.opB(lastValue).then(lastValue => {
                        sum -= lastValue*3;
                        return lib.isC().then(res => {
                            if (res) {
                                console.log("Do C()");
                                return lib.opC().then(res => {
                                    sum += res;
                                    return loop(lib, sum, lastValue);
                                });
                            }
                            return loop(lib, sum, lastValue);
                        });
                    });
                }
            });
        } else {
            console.log("All completed!");
            return Promise.resolve()
        }
    })(new Service(), 20, 0);
    

    幸运的是,您的循环中没有 break/continue/return 的形式,因为这会使它变得更加复杂。一般来说,将所有语句转换为延续传递样式,然后您可以在必要时将它们推迟。

    【讨论】:

    • 感谢您的示例。 loops become recursion,有趣的控制结构方法。我开始慢慢地开始思考这一切。不幸的是,调用顺序/结果与原始代码不同。我修改了我的代码并添加了一个比较方法来测试调用顺序/结果。
    • @jsjslim:是吗?我目前无法运行代码,您可以将结果发布到某个地方吗?
    • 比较:imgur.com/XguK6Lx。看起来第一个循环很好(doA()),第二个循环很好(isB()> doB()> doC()),但是到第三个循环,总和是关闭的
    • @jsjslim:啊,发现我的错误 - lastValue 是错误的。如果嵌套正确,则范围是正确的 :-) 或者,我可以使用 p.then(r =&gt; { x = r;,而不是将 x = await p 转换为 p.then(x =&gt;,然后链接版本也可以正常工作。
    猜你喜欢
    • 2016-11-27
    • 1970-01-01
    • 2013-07-04
    • 2018-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多