1

got one newbie question here. I need to declare the const in the top level of my Typescript file, so it´s accesible by all functions below. The problem is that the value I need to assign to this constant gets returned by the asynchronous function and this is where I got stuck. Asynchronous function cannot be called from the top-level - I can do something like this, but in this case, the constant is no more on the top level and cannot be accessed by other functions.

(async () => {
    const myConst = await asyncFunction(params);
})(); 

The other option is to use let instead of const, like this, but I rather had const up there

let myConst;
(async () => {
    myConst = await asyncFunction(params);
})();

Could you advise me? Is there some way out of it so I can declare the const for the global scope and assign it a value based on the async function?

Thanks a lot :)

Jozef
  • 479
  • 1
  • 9
  • 36
  • 3
    Generally what you want to do is not really useful, because the global variable will only be available after the asynchronous operation is complete. – Pointy Oct 15 '18 at 15:22
  • Closely related: [Javascript set const variable inside of a try block](https://stackoverflow.com/q/40925094) – apsillers Oct 15 '18 at 15:23
  • This is by design. You aren't meant to mix sync and async code in this fashion. If you need the variable in the future, you use a Promise to describe that, and the way you normally do that, is by using `async` functions. This way you can `await` on the value to ensure that it's there (as opposed to your current approach, in which you can only guess, or poll) – Madara's Ghost Oct 15 '18 at 15:23
  • Either use a promise, either wrap the code that needs that constant specifically inside the async block. This is intended by design, as far as I know. – briosheje Oct 15 '18 at 15:23

3 Answers3

5

Using an async result in a top-level variable is problematic because there is no static guarantee that the result will be ready when the functions in your module attempt to read the value. In my opinion the proper & safe way to do this would be to assign a promise to the top-level variable, and make any function that reads the value await the result:

const myConst = asyncFunction(params);

async function example() {
  doStuff(await myConst);
}

But if it is important to have the async result in a top-level variable, and you have some way to guarantee that the value will be ready before it is read then I would go with your let assignment. I don't think there is any good way to make that variable a const in that case.

Jesse Hallett
  • 1,857
  • 17
  • 26
  • Thank you, now I understand it better :) – Jozef Oct 16 '18 at 09:25
  • 1
    It's 2022 now, and I'm pleased to [report that all major browsers now support "top-level `await`"](https://caniuse.com/?search=top%20level%20await) ([in modules, at least](https://tc39.es/proposal-top-level-await/)) so it's now safe to use `const myConst = await asyncFunction(params);` in the root/global scope (again, only in module script files). – Dai Feb 01 '22 at 17:59
2

The problem is that since the operation providing the value is asynchronous, the other functions in your module could try to use the variable/constant before its value was set. So you're best off avoiding doing this, perhaps by doing what Jesse Hallett suggests and forcing the other functions in the module to use the promise.

If you can't avoid it, and you know for sure those functions won't be called before the asynchronous operation getting the value returns, use the let option initializing the variable with null or some other flag value, and check for that flag value in the functions that supposedly aren't called until it's ready and have them throw. E.g.:

let myConst = null; // null or some other value that isn't valid for this
(async () => {
    try {
        myConst = await asyncFunction(params);
    } catch (e) {
        // Handle the fact the async function failed -- don't skip this!
    }
})();

function assumeMyConst() {
    if (myConst === null) {
        throw new Error("State error: myConst not available yet");
    }
}

function foo() {
    assumeMyConst();
    // ...
}

But again, avoid it if you possibly can.


Side note: Notice the error checking in that async IIFE. Don't skip error checking. :-)

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

Since myConst gets its value after async code completes, it is of Promise type. Hence your declaration should be pretty simple:

declare const myConst: Promise<any> // or type of the value instead of any

Use it anywhere like this:

myConst.then((v) => console.log(v))

P.S. My personal advice is to use modules (import/export) whenever possible. Global variables is a code smell.

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • There's a third of a good answer there. Can you finish it? Explaining why you'd use a promise for this, and what problem it solves, and how the functions relying on the information would use that promise? *Edit:* [Jesse Hallett](https://stackoverflow.com/a/52820032/157247)'s posted a complete version of this approach. – T.J. Crowder Oct 15 '18 at 15:28
  • Ok, I'll finish it, sorry – Nurbol Alpysbayev Oct 15 '18 at 15:29