Before the introduction of top-level await, if you ever try to use the
await keyword outside of an
async function, you would receive a Syntax error. To avoid this, developers used Immediately Invoked Function Expressions(IIFEs).
But the above instance and solution is just the tip of the iceberg.
When you work with ES6 modules, there will be ample instances where you will be exporting values and importing them. Let’s look at such an example.
In the above example, we export and import variables between
middleware.js . You can name the files in any way you want.
If you carefully have a look, you would notice a
delay function that basically returns a Promise which would resolve after a timeout. Since it is asynchronous, we use the
await keyword inside our async IIFE to wait until the execution completes. In a real-world example, the promise would be replaced by a fetch call or a task of asynchronous nature. Once the promise is resolved, we assign values to our variables which are computed from the functions imported from
library.js . This basically means that our variables are
undefined until the promise gets resolved.
When you have a look at the end of the above code snippet, you will see that the variables we have calculated are being exported, so that another module can use them.
Let’s look at a module that imports and uses the above-exported variables.
If you run the above code snippet, you will notice that the first two log statements are undefined and the latter print values 169 and 13. How does this happen?
This is because the exports from the module
middleware.js are accessed by
main.js before the completion of the async function. Remember we had a promise waiting to be resolved…
To solve this problem, we should somehow notify the modules importing these variables when it is ready to access them.
There are two commonly used workarounds available for the above problem.
1.Export a Promise to represent initialization
You can export the IIFE and can use that to identify when the exports are ready to be accessed. The
async keyword makes a method asynchronous, which in turn always returns a promise. This is why the async IIFE returns a promise in the below solution.
When you access these exports from
main.js , you can wait for the async IIFE to resolve and then access the variables.
Although the above solution does the job, it introduces some new problems.
- Everyone should start following this pattern as a standard as you have to find the right promise to wait.
- If another module depends on the variables
main.js, we should make sure we re-export the IIFE promise as well. So that the other module too knows when to access our variables.
There is another workaround that solves the above-said issues.
2.Resolve the IIFE promise with the variables that should be exported
In this workaround, rather than exporting the variables separately, we return them from our async IIFE. This allows our
main.js file to simply wait for the promise to resolve and retrieve the value.
But this solution too has its own set of complications.
According to the proposal, “this pattern has the undesirable effect of requiring a broad reorganization of the related source into more dynamic patterns and placing much of the module body inside the
.then() callback in order to use the dynamically available imports. This represents a significant regression in terms of static analyzability, testability, ergonomics, and more, compared to ES2015 modules”.