Top-level await support was added to Node.js in 14.3.0 via --experimental-top-level-await
and later to --harmony-top-level-await
.
The Problem
I need to use a top level await in my ESM script file, if it is supported by the current Node.js runtime. And further, I need to set a boolean flag to indicate that the promise was successfully awaited at the top level.
An example of what I mean:
let topLevelAwaitEnabled;
try {
await Promise.resolve(); // replaced with an actual promise
topLevelAwaitEnabled = true;
} catch (ignored) {
topLevelAwaitEnabled = false;
}
console.log(topLevelAwaitEnabled);
// carry on with the rest of the application, regardless of success or failure
// at some point, topLevelAwaitEnabled is checked to conditionally execute some code
If top level await support is enabled, this succeeds fine. However, if it is not supported, this will result in the following error during parsing and cannot be caught at runtime with a try
/catch
:
$ node test.js...\test.js:3
await Promise.resolve(); // replaced with an actual promise
^^^^^
SyntaxError: await is only valid in async function
So the question is: How can I use a top level await if it is supported, without creating incompatibility issues with Node.js runtimes that do not support top level await (either no CLI flag was specified or simply no runtime support)?
If the answer is "it is not possible", I would like an explanation as to why this is impossible.
In the case I am actually committing an XY problem, the underlying issue is I need a top-level dynamic import.
Note: I am well aware that top level await is not recommended for a variety of reasons, however it is crucial for a specific functionality of my application and does not impose any issue with my use case. Alternatives will likely not suffice.
Attempts
I have tried the following methods, to no avail:
eval
: I have tried replacing the await line with aneval("await Promise.resolve()")
, in the hope the code was evaluated in the current context. Unfortunately, even if top level await is supported, this will result in the same error, as it does not seem to inherit the current context.vm.compileFunction
: Same issue waseval()
, top level await is not supported.vm.SourceTextModule
: Evaluation is asynchronous and would need to be awaited at the top level to check if it is supported... which is a catch 22.- conditional execution of the
await
based onprocess.version
andprocess.execArgv
: The error during parsing - it never actually executes the code, so conditional execution is ruled out.
getGlobalPreloadCode()
hook is unfortunately not asynchronous and is invoked immediately after my script is loaded. Since dynamic import is asynchronous, I am unable to delegate thegetGlobalPreloadCode()
call other ESM loaders, as they are not yet loaded. The remaining hooks are async, and I can await inside of the hooks. – Fruitrequire.resolve('node:test')
or similar. Another way to go about it is to create a function that uses import, run it and see if it returns an error. – Nomo