Problem
JavaScript has no expression-oriented blocks. When computing a value through conditionals or multiple steps, you're stuck with either mutable let bindings or IIFEs:
// Mutable — result is `let` for the rest of the scope
let result: string;
if (user.role === "admin") {
result = formatPerms(await fetchAdminPerms(user.id));
} else {
result = "none";
}
// IIFE — noisy, hard to scan
const result = (async () => {
if (user.role === "admin") return formatPerms(await fetchAdminPerms(user.id));
return "none";
})();
IIFEs are widely considered a code smell in modern JavaScript:
Examples in other languages
| Language |
Feature |
Example |
| Kotlin |
run { } |
val x = run { ... } |
| Rust |
Block expressions |
let x = { ... }; |
| Scala |
Block expressions |
val x = { ... } |
| Ruby |
Inline blocks |
x = begin ... end |
Proposal
// Sync
const result = call(() => {
if (user.role === "admin") return "full";
if (user.role === "editor") return "partial";
return "none";
});
// Async
const result = await callAsync(async () => {
const perms = await fetchPerms(user.id);
const roles = await fetchRoles(user.id);
return merge(perms, roles);
});
Implementation:
export function call<R>(fn: () => R): R {
return fn();
}
export async function callAsync<R>(fn: () => Promise<R>): Promise<R> {
return await fn();
}
The implementation is trivial — but so are noop, identity, and attempt. The value is naming the pattern, not the complexity of the code. call(() => ...) communicates intent where (() => ...)() hides it in punctuation.
Precedent
es-toolkit already wraps simple patterns into named utilities:
attempt(fn) — wraps try/catch
noop() — wraps () => {}
identity(x) — wraps (x) => x
call(fn) fits the same category.
Problem
JavaScript has no expression-oriented blocks. When computing a value through conditionals or multiple steps, you're stuck with either mutable
letbindings or IIFEs:IIFEs are widely considered a code smell in modern JavaScript:
eslint-plugin-no-iifebans them outright, calling IIFEs "a messy remnant of early Javascript" that "only serves to decrease readability"unicorn/consistent-function-scopingconflicts with IIFEsExamples in other languages
run { }val x = run { ... }let x = { ... };val x = { ... }x = begin ... endProposal
Implementation:
The implementation is trivial — but so are
noop,identity, andattempt. The value is naming the pattern, not the complexity of the code.call(() => ...)communicates intent where(() => ...)()hides it in punctuation.Precedent
es-toolkit already wraps simple patterns into named utilities:
attempt(fn)— wrapstry/catchnoop()— wraps() => {}identity(x)— wraps(x) => xcall(fn)fits the same category.