JAVASCRIPT / FUNCTIONS
Immediately invoked function expressions
Write IIFEs on purpose: why the wrapping parentheses are required, what an IIFE returns, and when one still beats a block or a module.
What you will learn
- Force expression position with wrapping parentheses before the call parentheses
- Expose an API from an IIFE by returning it, keeping inner state unreachable
- Guard against ASI by ending the previous line with ; or starting the IIFE with ;
- Await inside a classic script with an async IIFE, and handle the promise it returns
Understanding Immediately invoked function expressions
JavaScript decides what the word `function` means from where it appears. At the start of a statement it begins a function declaration, which must have a name and which evaluates to nothing, so there is no value for a trailing `()` to call, and `function () {}()` never even parses. Wrapping the literal in parentheses puts it in expression position, where it evaluates to a function object, and the `()` right after calls that object on the spot. Any operator does the same job, including `!`, `void`, `+`, and the right-hand side of an assignment, but parentheses are conventional because they neither coerce nor discard the result.
The mental model is a scope you run once and throw away. Every call builds a fresh environment for its parameters and inner declarations, so anything declared inside an IIFE cannot be seen from outside, including `var` and inner function declarations, which ignore blocks. The single exit is the return value: whatever you return is all the surrounding code gets, and returned functions keep operating on the inner bindings after the call has finished. That pairing of a private environment with a deliberately narrow return value is what used to be called the module pattern.
Most of the original motivation is gone. `let` and `const` are block-scoped, so a bare `{ ... }` block hides bindings without a function call, and every ES module already has its own scope, so wrapping module code buys nothing. What remains is still useful: building a `const` whose value takes several statements to compute, using `await` in a classic script or CommonJS file through an `async` IIFE, wrapping bundler output, and naming a function expression so it can recurse in a place where only an expression is allowed.
const counter = (function () {
let count = 0; // private: nothing outside can reach this binding
return {
increment() { return ++count; },
current() { return count; }
};
})();
console.log(counter.increment());
console.log(counter.increment());
console.log(counter.current());
console.log(typeof count); // the name never reached the outer scopeParentheses move a function literal from statement position into expression position, turning it into a value you can call at once to get a single-use private scope whose only output is its return value.
Worked examples
What the parentheses actually fix
Shows that the problem is grammatical position, and that several tokens can solve it.
try {
eval('function () {}()'); // declaration: needs a name, cannot be called here
} catch (e) {
console.log(e.name);
}
!function () { console.log('bang'); }();
void function () { console.log('void'); }();
(function () { console.log('outer parens'); })();
(function () { console.log('inner parens'); }());Example explained
Line 1The eval string starts with `function` in statement position, so the parser demands a declaration name and rejects the source before running anything.
Line 2`!` and `void` put the literal in expression position just as well as parentheses do; the call runs and its result is then negated or discarded.
Line 3Both paren placements work because a function expression is a valid call target, so the call parentheses can sit inside or outside the wrapper.
The semicolon trap
Demonstrates how automatic semicolon insertion glues an IIFE onto the previous line.
const rows = 2
;(function () {
console.log('IIFE ran with rows = ' + rows);
})()
try {
const cols = 2
(function () {
console.log('never printed');
})()
} catch (e) {
console.log('without the semicolon: ' + e.name);
}Example explained
Line 1The leading `;` terminates the `const rows = 2` statement, so the IIFE is parsed as its own expression statement and runs normally.
Line 2In the try block no semicolon ends `const cols = 2`, and `(` is a legal continuation, so the parser reads the initializer as `2(function () { ... })()`.
Line 3That is valid syntax but calls the number 2, which fails at runtime with a TypeError rather than at parse time.
Async IIFE in a classic script
Uses an async IIFE to await at the top level and shows when each line runs.
function delay(ms) {
return new Promise(function (resolve) { setTimeout(resolve, ms); });
}
(async function () {
console.log('inside, before await');
await delay(10);
console.log('inside, after await');
})();
console.log('outside, after the IIFE statement');Example explained
Line 1`async` changes nothing about the grammar rule, so the function expression still needs the wrapping parentheses.
Line 2An async body runs synchronously until the first `await`, which is why the first line inside prints before anything else.
Line 3`await` returns control to the caller, so the statement after the IIFE runs while the timer is still pending.
Line 4The IIFE evaluates to a promise that is thrown away here, so nothing is watching for a rejection.
Important notes
A `function` IIFE creates its own `this` and `arguments`, while an arrow IIFE inherits both from the surrounding code, so switching between the two forms can quietly change what `this` means.
An async IIFE evaluates to a promise. Since nothing awaits it, a throw inside becomes an unhandled rejection, reported as a console error in browsers and a process-level warning or crash in Node.
Common mistakes
Writing `function () { ... }()` at the start of a statement: the parser reads a declaration, demands a name, and the whole file fails to parse, so even the lines above it never run.
Leaving the previous line without a semicolon: `const n = 3` followed by `(function () { ... })()` is parsed as `3(function () { ... })()`, which throws a TypeError because a number is being called.
Using the inner-call form with an arrow, `(() => { ... }())`: an arrow function is not a valid call target, so that is a SyntaxError, and the call parentheses must go outside as in `(() => { ... })()`.
Try it yourself
Change, predict, then run
In a browser console, write a `nextId` IIFE that keeps a counter inside and returns a function producing 'id-1', 'id-2', 'id-3' on successive calls. Then check that `typeof count` is 'undefined' from outside, proving the counter cannot be read or reset.
Open the JavaScript workspaceCheck your understanding
Why does `!function () { console.log('hi'); }()` run the function when removing the `!` turns the same line into a syntax error?
- `!` makes the parser read `function` as the start of an expression, and only an expression produces a value that `()` can call
- `!` hoists the function so it is already defined by the time the call parentheses are reached
- `!` converts the function to a boolean, and booleans can be invoked like functions
- `!` suppresses the return value, which is what the parser rejects in the plain version
Show answer
The `!` does nothing to the function itself; it changes how the parser classifies the token `function`. In statement position `function` begins a declaration, which requires a name and yields no value to call, while in expression position it evaluates to a function object. Hoisting is the tempting wrong answer because declarations really are hoisted, but hoisting is exactly what you get instead of a callable value here, and it is not something `!` provides; in fact `!` applies to the result of the call, since calls bind tighter than unary operators.