Typescript: Bug in async/await when doing a "return" statement inside of a for loop that awaits in an async function

Created on 19 Aug 2015  Â·  3Comments  Â·  Source: microsoft/TypeScript

In current prototype of the Async/Await downlevel support, we found a bug causes an erroneous exception to occur in code that should otherwise work.

Repro:

The gist of the issue is that having a “return” statement inside of a for loop in a function that has an await in it causes an error.

Run this code an you’ll see that whenever the “num” is > .5 (and the “return true” statement gets evaluated), you get the following error that comes from within the __generator code: “TypeError: Unable to get property '0' of undefined or null reference”

    async function itemExists(): Q.Promise<boolean> {
        var numTries = 3;
        for (var i = 0; i < numTries; i++) {
            var num = await Q.fcall(function() { return Math.random() });
            console.log(num);
            if (num > .5) {
                return true;
            }
        }
        return false;
    }

    itemExists().then(function(val) {
        console.log("All values greater than .5: " + val)
    }).catch(function(e) {
        console.error("Error: " + e);
    });

Analysis:

The problem is caused by the “return” statement within the loop. If I had modified itemExists() to set a local variable, for example, and then break out of the loop via a “break” statement, the code would work:

    // With workaround (NOT short-circuiting the loop)
    async function itemExists(): Q.Promise<boolean> {
        var numTries = 3;
        var result = false;
        for (var i = 0; i < numTries; i++) {
            var num = await Q.fcall(function() { return Math.random() });
            console.log(num);
            if (num > .5) {
                result = true;
                break;
            }
        }
        return result;
   }

A quick analysis of the issue seems to be in the generated code. The exception is throw in here:

            try {
                var operation = body(state);
                opcode = operation[0], arg = operation[1];
            } catch (e) {
                opcode = 1 /*throw*/, arg = e;
            }

But the actual culprit seems to be in the fact that there is an inconsistency in the generated return and break statement (so far as I can tell). Namely, 3 looks like sometime a “break” and sometimes a “return”.

function itemExists() {
    return new Q.Promise(function (_resolve) {
        _resolve(__awaiter(__generator(function (_state) {
            switch (_state.label) {
                case 0:
                    numTries = 3;
                    i = 0;
                    _state.label = 1;
                case 1:
                    if (!(i < numTries))
                        return [3 /*break*/, 4];
                    return [4 /*yield*/, Q.fcall(function () {
                        return Math.random();
                    })];
                case 2:
                    num = _state.sent;
                    console.log(num);
                    if (num > .5) {
                       return [3 /*return*/, true];
                    }
                    _state.label = 3;
                case 3:
                    i++;
                    return [3 /*break*/, 1];
                case 4:
                    return [2 /*return*/, false];
            }
        })));
    });
    var numTries, i, num;
}
itemExists().then(function (val) {
    console.log("All values greater than .5: " + val);
}).catch(function (e) {
    console.error("Error: " + e);
});
Bug Fixed

Most helpful comment

@d180cf we are working on it :)

All 3 comments

This state machine looks like a downlevel emit of generators. Does it mean that [email protected] is going to support yield when targeting es5?

@d180cf we are working on it :)

This should no longer be an issue as of 4685646, which is the new version of these downlevel transformations for async functions we are targeting for our 2.1 release.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rigdern picture rigdern  Â·  3Comments

wmaurer picture wmaurer  Â·  3Comments

DanielRosenwasser picture DanielRosenwasser  Â·  3Comments

CyrusNajmabadi picture CyrusNajmabadi  Â·  3Comments

Roam-Cooper picture Roam-Cooper  Â·  3Comments