How toUse an `iife` Helper for Inline Control Flow in TypeScript
JavaScript still does not have do expressions. If you have seen the do expressions proposal, the idea is simple: write a block of code and get a value back from it as an expression.
This would be useful in a lot of places. Assigning a variable, building an object property, or returning JSX often needs if, try/catch, or a few local variables, but JavaScript only accepts expressions there.
Since we do not have do expressions today, and may never get them, we can write a small helper function to let us achieve the same.
Create the Helper
You could write an actual IIFE every time you need it.
let result = (() => {
if (input === "") return "empty";
return input.trim();
})();
That works, but the syntax is a bit awkward to read and write. A small helper gives you the same idea with a shape that feels easier to scan.
export function iife<T>(fn: () => T): T {
return fn();
}
That is all it does. The benefit is that it gives you the same pattern with less syntax, while keeping the code close to the place where the value is needed.
Compute a Value Without Hoisting a Variable
One of the most common cases is variable initialization. Usually this means declaring a variable first and assigning it later.
let buttonLabel: string;
if (isSaving) {
buttonLabel = "Saving...";
} else if (hasChanges) {
buttonLabel = "Save changes";
} else {
buttonLabel = "Saved";
}
This works, but the variable has to exist before the logic that computes it. With iife, the assignment and the control flow stay together.
let buttonLabel = iife(() => {
if (isSaving) return "Saving...";
if (hasChanges) return "Save changes";
return "Saved";
});
This is a small example, but it shows the shape clearly. The whole block exists to produce a single value.
Branch in the Middle of JSX
The helper is more useful in JSX when you need to branch in the middle of a larger tree, not when you can just return early from the component.
<header>
<h1>{team.name}</h1>
<p>
{iife(() => {
if (team.plan === "enterprise") return "Priority support enabled";
if (team.trialEndsAt) return `Trial ends on ${formatDate(team.trialEndsAt)}`;
return "Standard support";
})}
</p>
</header>
This is the kind of JSX where iife helps. You are already inside a larger return value, and only one small part needs block-style logic.
Use try/catch Inside an Assignment
try/catch is another place where this helps. Without iife, you usually need a variable outside the block so both try and catch can assign to it.
let result: { data: string | null; error: string | null };
try {
let data = JSON.parse(input) as { name: string };
result = { data: data.name, error: null };
} catch {
result = { data: null, error: "Invalid JSON input" };
}
With iife, the assignment and the control flow stay together.
let result = iife(() => {
try {
let data = JSON.parse(input) as { name: string };
return { data: data.name, error: null };
} catch {
return { data: null, error: "Invalid JSON input" };
}
});
This is one of the clearest uses for the pattern. The block exists to compute one value, so keeping everything inside the assignment reads well.
Await Sequential Async Work Inline
The same idea works with async code. If the callback is async, iife returns a promise, so you can await it.
You could move this logic into helpers like fetchViewer and fetchProfile, but then those helpers need more arguments because they no longer have access to the current scope. Keeping the inline flow inside iife can help keep those extracted functions simpler.
interface Viewer {
id: string;
profileUrl: string;
}
interface Profile {
name: string;
}
let viewer = await iife<Promise<Viewer>>(async () => {
let response = await fetch("/api/viewer", {
headers: request.headers,
});
return await response.json();
});
let profile = await iife<Promise<Profile>>(async () => {
let response = await fetch(viewer.profileUrl, {
headers: request.headers,
});
return await response.json();
});
Each block only keeps the variables it needs. You do not need names like viewerResponse and profileResponse, and you also do not need to extract small helpers just to avoid outer mutable state.
If the requests are independent, Promise.all is still the better tool. This pattern helps when the work is sequential and each computed value needs its own local setup.
Keep Inline Logic Small
This pattern works best when the inline block is short and only exists to compute one value. A couple of branches, a try/catch, or a small async sequence are usually fine.
Once the block starts growing, the trade-off changes. At that point, a named function is usually easier to understand because it gives the logic a clear boundary and a name that explains why it exists.
Final Thoughts
An iife helper is a small pattern, but it solves a real problem. It gives you a way to use block-style logic where JavaScript only accepts expressions, without introducing outer mutable state.
Do you like my content?
Your sponsorship helps me create more tutorials, articles, and open-source tools.