oxc-project/oxc · error · OxcDiagnostic

Unexpected {} in "{}" computed property.

Error message

Unexpected {} in "{}" computed property.

What it means

Diagnostic from oxlint's vue/no-async-in-computed-properties rule (since oxlint 1.71.0). It fires when a computed property (object entry under `computed`) contains asynchronous work: an async function declaration, an await operator, a Promise object, a .then/.catch/.finally chain, or a timed function like setTimeout. Vue computed properties must be synchronous; async ones return a Promise that never updates reactively, producing values that render as empty or stale forever.

Source

Thrown at crates/oxc_linter/src/rules/vue/no_async_in_computed_properties.rs:42

    NewPromise,
    Asynchronous,
    Timed,
}

impl AsyncKind {
    fn as_str(self) -> &'static str {
        match self {
            Self::AsyncFunction => "async function declaration",
            Self::Await => "await operator",
            Self::NewPromise => "Promise object",
            Self::Asynchronous => "asynchronous action",
            Self::Timed => "timed function",
        }
    }
}

fn unexpected_in_property(span: Span, kind: AsyncKind, key: &str) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!("Unexpected {} in \"{}\" computed property.", kind.as_str(), key))
        .with_label(span)
}

fn unexpected_in_function(span: Span, kind: AsyncKind) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!("Unexpected {} in computed function.", kind.as_str()))
        .with_label(span)
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
struct NoAsyncInComputedPropertiesConfig {
    /// Names of identifiers whose member-call chains (`.then` / `.catch` / `.finally`)
    /// should be ignored. Useful for libraries like Zod where `.catch(default)` is
    /// a builder API, not a Promise method.
    ignored_object_names: FxHashSet<String>,
}

#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)]

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Move the async work into a method, watch/watchEffect, or onMounted and store the result in a ref/data property
  2. Keep the computed synchronous and derive from already-loaded state
  3. If the flagged member chain is a builder API (e.g., zod's .catch), add the object name to ignoredObjectNames: { "ignoredObjectNames": ["z"] }

Example fix

// before
computed: {
  async user() { return await fetchUser(this.id) },
}
// after
data: () => ({ user: null }),
watch: {
  id: {
    immediate: true,
    async handler(id) { this.user = await fetchUser(id) },
  },
}
Defensive patterns

Strategy: validation

Validate before calling

# scan computed blocks for async constructs
grep -rnA20 "computed: *{" --include='*.vue' src | grep -nE "async |await |\.then\(|new Promise|setTimeout"

Prevention

When it happens

Trigger: `computed: { foo: async function() {...} }`, a computed whose body contains `await`, `new Promise(...)`, `someCall().then(...)`, or setTimeout/setInterval, where the object is a Vue component options object.

Common situations: Loading data inside computed because it 'derives' from props; chaining a Promise-returning API in a getter; accidentally flagging builder APIs like Zod's .catch() — the rule's ignoredObjectNames config (ignored_object_names) exists exactly to whitelist those.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/2a47b75cab84b380. Report an issue: GitHub.