oxc-project/oxc · warning · OxcDiagnostic
Prefer top-level await over using an async IIFE.
Error message
Prefer top-level await over using an async IIFE.
What it means
This is the async-IIFE diagnostic of oxlint's `unicorn/prefer-top-level-await`. It flags the historical pattern `(async () => { ... })()` (or `(async function () { ... })()`) used only to get `await` at module top level, and recommends top-level `await` in ES modules instead, which removes the wrapper and its scoping/error-handling quirks.
Source
Thrown at crates/oxc_linter/src/rules/unicorn/prefer_top_level_await.rs:19
use oxc_ast::{
AstKind,
ast::{Expression, VariableDeclarationKind},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use crate::{
AstNode, ast_util::is_method_call, ast_util::variable_declaration_kind, context::LintContext,
rule::Rule,
};
fn prefer_top_level_await_over_promise_chain_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Prefer top-level await over using a promise chain.").with_label(span)
}
fn prefer_top_level_await_over_async_iife_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Prefer top-level await over using an async IIFE.").with_label(span)
}
fn prefer_top_level_await_over_async_function_call_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Prefer top-level await over an async function call.")
.with_help("Add `await` before the function call.")
.with_label(span)
}
#[derive(Debug, Default, Clone)]
pub struct PreferTopLevelAwait;
declare_oxc_lint!(
/// ### What it does
///
/// Prefer top-level await over top-level promises and async function calls.
///
/// ### Why is this bad?
///View on GitHub (pinned to e1e7af627c)
Solutions
- Unwrap the IIFE and `await` its body directly at module top level; move the `.catch` into a `try { ... } catch` block or keep a top-level `await promise.catch(...)` if you need to swallow errors.
- Confirm the file is ESM and the runtime supports top-level await before applying.
- If the IIFE exists to create a scope (name isolation), keep it and disable the rule for the line, or use a block `{ ... }` plus `await`.
Example fix
// before
(async () => {
await init();
await migrate();
})();
// after
await init();
await migrate(); Defensive patterns
Strategy: validation
Validate before calling
// In ESM, drop the async IIFE wrapper await init(); await migrate(); // CI: npx oxlint --deny-warn unicorn/prefer-top-level-await src/
Try / catch
// Replace IIFE .catch with a top-level try/catch
try {
await init();
} catch (err) {
console.error('boot failed', err);
process.exitCode = 1;
} Prevention
- Treat `(async () => {...})()` at module scope as a migration artifact in ESM projects.
- Preserve name isolation with a plain block `{ ... }` if that was the IIFE's purpose.
- Keep error handling explicit — an un-caught rejected top-level await fails the module load.
When it happens
Trigger: A top-level expression statement that is an immediately-invoked async arrow or function expression in a module; the rule pairs this with the promise-chain and async-call diagnostics from the same file to cover the three common pre-top-level-await idioms.
Common situations: Legacy bootstrap code like `(async () => { await init(); await migrate(); })().catch(console.error);` written before Node 14.8/bundler TLA support; after migrating the package to ESM these wrappers become dead weight and obscure stack traces.
Related errors
- Prefer top-level await over using a promise chain.
- Prefer top-level await over an async function call.
- IIFE with parenthesized arrow function body is considered un
- Prefer `Blob#{good_method}()` over `FileReader#{bad_method}(
- Async assertions must be awaited.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/c5f073b41c983223.
Report an issue: GitHub.