[FIXED] Variable is used before being assigned error even though they are being set inside awaited Promise.all

Issue

I have two async methods that are independent of each other that I would like to run simultaneously to save time. However, to continue with the following step, I need to wait for both the return values from the two methods. My code looks something like this:

public async methodOne(): Promise<MyObjectOne> {
    ... do something interesting
    return one;
}

public async methodTwo(): Promise<MyObjectTwo> {
    ... do something interesting
    return two;
}

public useVariables(one: MyObjectOne, two: MyObjectTwo) {
    ... do some useful work
}

public async runTogether(): Promise<void> {
    let one: MyObjectOne;
    let two: MyObjectTwo;
    await Promise.all([this.methodOne(), this.methodTwo()])
        .then((values: [MyObjectOne, MyObjectTwo]) => {
            values.map((value: MyObjectOne | MyObjectTwo) => {
                if (value instanceof MyObjectOne) {
                    one = value;
                } else {
                    two = value;
                }
            });
        })
        .catch((error) => {
            throw new Error('There was an error');
        });
    this.useVariables(one, two);
}

Based on my best understanding, by the time the program reaches the last line this.useVariables(one, two);, either both the variables are set or an error has been thrown.

But the IDE is indicating on both variables that the Variable 'one/two' is used before being assigned.

I can check for undefined just before using the two variables to get rid of the errors. But I’m wondering if this message is indicating that I’ve missed something. Or is it simply that the IDE does not recognizing that the variables are set? Alternatively, is there a better way to run the two methods simultaneously?

Solution

Assigning to an outside variable inside a callback is somewhat of a code smell, and also something that TypeScript has moderate issues with. It can’t tell whether the callback will run, and so will produce that warning.

For a smaller example that produces the same error, see:

let numOuter: number;
[].forEach((num) => {
  numOuter = num;
});
console.log(numOuter); // Variable 'numOuter' is used before being assigned.

The error is more justified in your case, because – what if an error is thrown? Then one and two will still be undefined – doing this.useVariables(one, two); would be unsafe, because you don’t know for sure if the process succeeded.

TypeScript works best when you use can structure code to use const whenever you can. Use instead:

public async runTogether(): Promise<void> {
    const [one, two] = await Promise.all([this.methodOne(), this.methodTwo()]);
    this.useVariables(one, two);
}

(make sure that methodOne is typed to return a type of MyObjectOne, and the same for methodTwo)

Because runTogether returns a Promise, the possible error that methodOne or methodTwo throw should almost surely be passed up the call chain to the caller, for the caller to handle – that way, the caller can see if there was a problem or not, and handle it appropriately. If you handle errors inside runTogether itself, the caller won’t be able to see that there was an error unless you re-throw. There are use cases for that, but it’s a bit ugly.

Answered By – CertainPerformance

Answer Checked By – Gilberto Lyons (Easybugfix Admin)

Leave a Reply

(*) Required, Your email will not be published