在打字稿中链接承诺

时间:2016-08-04 10:03:14

标签: typescript promise async-await

如何将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代码以使用链式承诺。

我可以转换如下的简单示例。

// 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()

2 个答案:

答案 0 :(得分:3)

感谢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(()=>{...})
    • 其中{...}await之后的代码(包括等待分配)
  • 这会导致一些深层嵌套,但如果我按照这些步骤操作,我发现自己不太可能犯错误
  • 完成并验证后,我可以进入并清理

Conversion

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

答案 1 :(得分:1)

await成为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形式,因为这会让它变得更加复杂。通常,将所有语句转换为连续传递样式,然后您可以在必要时将它们推迟。