1

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?

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
Steven
  • 714
  • 2
  • 8
  • 21
  • Does this answer your question? [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – Jared Smith Aug 30 '22 at 00:33
  • Also https://stackoverflow.com/questions/33198849/what-is-the-temporal-dead-zone – Jared Smith Aug 30 '22 at 00:33
  • 1
    OP is properly `await`ing the asynchronous calls and assigning the values. The problem is getting TypeScript to recognize it properly. I don't believe either of those duplicate targets are appropriate for this question. – CertainPerformance Aug 30 '22 at 00:42

1 Answers1

4

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.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • I hadn't considered/realized that the `await Promise.all()` would simply return the values in an array. Thanks for the clear explanation. – Steven Aug 30 '22 at 01:01