Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Zlatkovsky opened this issue Aug 19, 2015 · 3 comments
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@Zlatkovsky
Copy link
Member

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);
});
@mhegazy mhegazy added the Bug A bug in TypeScript label Aug 19, 2015
@mhegazy mhegazy added this to the TypeScript 1.7 milestone Aug 19, 2015
@d180cf
Copy link

d180cf commented Aug 20, 2015

This state machine looks like a downlevel emit of generators. Does it mean that tsc@1.7 is going to support yield when targeting es5?

@mhegazy
Copy link
Contributor

mhegazy commented Aug 20, 2015

@d180cf we are working on it :)

@rbuckton
Copy link
Member

rbuckton commented Sep 7, 2016

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.

@rbuckton rbuckton closed this as completed Sep 7, 2016
@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Sep 7, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

4 participants