Pretty much all the common reasons for using an IIFE in my code have been replaced with block-scoped variables.
The ONE use I have seen in real code is to simulate top-level await
:
(async function() {
try {
const data1 = await something1();
const data2 = await something2(data1);
...
} catch(e) {
...
}
})();
The remaining reasons I see for an IIFE are when you want the advantages of a function such as using return
or throw
to stop execution of a defined portion of the remaining code (without nesting in an if
) or when you want to call something recursively. But, of course, you could get all of those advantages with a locally scoped named function too.
But, if the code in your function benefits heavily from accessing parent scoped variables, then it really wants to be inline and that creates a possible reason for an IIFE to combine the advantages of being in a separate function with inline access to parent scope.
Now, in reality, most/all of these cases can also be solved with a little refactoring to either define the named function in the local scope (so it still has access to parent scoped variables) or just pass things from the parent scope to a named function so it has access to them that way and, in fact, that's what we all do in other languages. The only cost to that is one more local function name (which can even be locally scoped if you want to hide it).